Ajax

Promise trong Javascript

promise

Sẽ là một thiếu sót trầm trọng khi nói tới Asynchronous operations trong Javascript mà không nhắc tới Promise. Kể từ khi được giới thiệu lần đầu tiên, Promise đã được cộng đồng JS devs đón nhận vô cùng nồng nhiệt, và đóng góp để cải tiến rất nhiều về cả độ ổn định lẫn hiệu năng. Nhờ Promise mà các function triển khai nhìn gọn gàng và đẹp mắt hơn rất nhiều so với dùng các callbacks chồng chất, mà thuật ngữ chuyên dụng để chỉ trường hợp này là callback hell.

1. Callback hell và sự ra đời của Promise:

Nếu bạn đã quen thuộc với callback hoặc chí ít đã đọc bài viết trước của tôi về callbacks, thì bạn sẽ thấy có không ít trường hợp phải vận dụng và móc nối các callbacks thành các chuỗi dài, lồng nhau theo tầng, tức là callback ông bà gọi callback cha mẹ, callback cha mẹ lại gọi callback con, callback con lại gọi callback cháu… theo mô hình dưới đây:

Chuỗi trên là một ví dụ của callback hell, có nghĩa là “a callback fires another callback which fires another callback which fires another other callback which fires another other other callback…”, tức là có quá nhiều callback được tổ chức theo hướng cái này gọi cái sau và lồng nhau như vậy. Tất nhiên, code như vậy chẳng có gì sai và cũng logic đó thôi, vì hành động nối tiếp hành động và bắt buộc phải dùng callback. Để hạn chế cho việc hại mắt, trước khi có Promise thì người ta thường tách hàm ra, chẳng hạn:

Như vậy, mặc dù vẫn là callback hell nhưng nhìn đỡ hại mắt hơn nhiều so với việc lồng ghép, lùi vào dãn ra quá nhiều, đặc biệt là body của mỗi function có thể dài tới 20 dòng thì người lạ đọc code của bạn sẽ phải đi bệnh viện mà cắt cặp mắt kính mà để sẵn trước khi đọc. Tuy nhiên, cách làm này cũng có nhược điểm so với callback hell là KHÔNG gom tất cả các functions thực thi vào một chuỗi, do đó người đọc sẽ phải cuộn tới lui coi ý tưởng của bạn là gì. Chẳng hạn, nội dung của dsth chỉ bao gồm doSomethingAsync, và mặc dù trong doSomethingAsync có chứa grandParentCallback, grandParentCallback lại chứa parentCallback, nhưng trong dsth thì không thấy có và người đọc đang dừng tới dsth thì vẫn chưa hiểu ý bạn lắm, còn việc childCallback xuất hiện ở đâu trong parentCallback thì họ lại phải tìm từng dòng một. Và Promise ra đời để giải quyết vấn đề đó.

2. Promise là gì?

A Promise is an object representing the eventual completion or failure of an asynchronous operation.

Diễn giải ra, thì Promise là một công cụ để bạn thực hiện một asynchronous operation và nó cung cấp cho bạn 2 công cụ để trả về kết quả (giống như return vậy) là operation đó có thành công hay thất bại. Có thể tạm hiểu (nhưng không đúng, chỉ là tạm cho là vậy) ở thời điểm này là Promise cung cấp công cụ để return một Boolean operation là true nếu thành công và false nếu thất bại vậy, để từ đó bạn có hướng xử lí theo kết quả của từng trường hợp.

Promise là một Object có constructor hẳn hoi. Và do đó, nếu bạn muốn đặt một đối tượng là một Promise thì phải return new Promise. Cấu trúc của Promise constructor là một function gồm 2 đối số (arguments). Thực chất cả 2 tên này đều là function và mỗi tên trong chúng sẽ chứa đúng 1 argument duy nhất thể hiện KẾT QUẢ của PromiseTên thứ nhất là function để gọi nếu khi bạn muốn đặt kết quả của Promise là thành công, và argument của nó chính là thành quả của Promise. Tên thứ hai là function để gọi nếu bạn muốn kết quả của Promise là thất bại, và argument của nó là lỗi mà Promise sẽ “thảy” ra. Có nhiều cách gọi cặp functions kia (theo thứ tự tương ứng), chẳng hạn (resolve, reject), hay (onFullFilled, onReject), v.v… Còn tôi thường gọi là (onSuccess, onError). Tất nhiên, tên nào cũng được, bạn muốn gọi là (neuThanhCong, neuThatBai) cũng không sao, quan trọng là phải nhớ thứ tự của chúng. Cụ thể:

Tất nhiên, trong body của Promise sẽ có thể có nhiều trường hợp trả ra onSuccess và tương tự như vậy, bạn cũng có thể gọi onError cho các trường hợp khác nhau. Chẳng hạn:

Như vậy là chúng ta đã tạo được một Promise cực kì đơn giản. Hãy “khắc cốt ghi xương” là 2 tham số trong function trong constructor của Promise, cái đầu là thành công, còn cái sau là thất bại, và chúng chứa đối số chính là kết quả bạn mong muốn từ Promise. Còn cách dùng Promise bạn hãy tiếp tục theo dõi bên dưới.

3. Sử dụng Promise:

Công thức chung mang tính kinh điển của Promise khá giống với cái hình màu tím bên trên. Nếu bạn lười cuộn lên thì… đây, nó là dư lầy:

Trong đó, cái result nằm trong function trong then chính là cái argument bạn truyền qua onSuccess (tức argument thứ nhất của function trong Promise), còn cái error thì tương tự, nó là argument của onError (tức argument thứ hai của function trong Promise). Nếu gom hết lại, thì bạn có cái sơ đồ (hay bạn muốn gọi bằng từ gì cũng được) như sau:

“Vậy nó có gì khác callback đâu? Tôi dùng callback luôn cho rồi, nhìn Promise có vẻ rối rắm hơn.” – Nếu bạn đang nghĩ như vậy thì cũng không sai trong trường hợp này, vì nếu chỉ trả ra và đem kết quả đi xử lí một lần thì dùng callback sẽ hay hơn. Nhưng nếu bạn có nhiều callback móc nối nhau, thì Promise sẽ nhìn gọn gàng hơn. Ở đây, sự gọn gàng không thể hiện ở phần body của mỗi function, mà được thể hiện ở chuỗi hành động khi gọi method. Cái hay và làm Promise gọn gàng là việc nếu function(result) trong .then lại return một Promise, thì bạn có thể tiếp tục .then trong chuỗi Promise. Chẳng hạn:

promise

Trong chuỗi trên, đương nhiên promise() sẽ return một Promise rồi. Nhưng không dừng lại ở đó, anotherPromise, promiseAgainfinalPromise, bản thân chúng cũng return một Promise và bạn cứ “đính” chúng hết vào chuỗi Promise xuất phát từ promise() gốc. Và điều hay nữa, là nếu các Promise trong .then “quăng, ném, thảy” ra error thì tất cả sẽ được “quy tựu” vào đúng cái .catch được định nghĩa đúng một lần đó với tham số error trong function(error). Do đó, nếu có nhiều error khác nhau trong chuỗi, bạn dùng switch – case trong function(error) là hay nhất. Vd:

Một điểm cộng nữa của chuỗi Promise là nếu .then được thì mới .then tiếp xuống dưới. Còn nếu không được thì rơi xuống .catch luôn mà không đá động gì tới các .then bên dưới nó. Tức là gặp lỗi ở khoảng nào thì dừng ở đó chứ không cố tình chạy tiếp. Và lỗi thì cứ là error trong function(error) mà thôi, cái nào cũng rơi xuống đó, cực kì tiện lợi.

Thực chất Promise cũng được cấu thành từ callback mà thôi. Cụ thể là mỗi function trong .then hay .catch đều là callback. Tuy nhiên, việc tạo chuỗi Promise (Promise chain) như vậy sẽ giúp bạn và người đọc dễ dàng hiểu các bước trong ý tưởng của bạn. Chẳng hạn, body của findLoverForXmas, getToKnowHer, buyHerSomeGift, askHerOut, askHerOutAgain, askHerOutOnXmasEve có thể phức tạp, tuy nhiên sự phức tạp đó chỉ mang tính nội bộ của chúng, còn người đọc thì sẽ biết ý bạn là “Tôi đi tìm bạn gái để đi chơi Giáng sinh, tìm được thì làm quen với cô ấy, làm quen được thì tặng quà, nhận quà rồi thì mời đi chơi, mời được lần đầu thì mời tiếp lần sau, mời được lúc đó thì mời đi chơi Giánh sinh, còn nếu có vấn đề thì tôi lại ôm trụ tiếp”.

4. Yêu cầu về phía trình duyệt:

Thực tế, Promise vẫn đang khá mới, với tuổi đời chỉ được gần 3 năm, tức là nó chính thức được trình làng vào đầu năm 2014. Do đó, nó yêu cầu Firefox ít nhất là v.27 và Chrome (và các trình duyệt nhân Chromium) cũng từ v.32 (nhân Chromium) về sau. Đối với Safari, thì yêu cầu tối thiểu là macOS Yosemite 10.10 và iOS 8 về sau. Edge thì ngon cơm rồi, còn trình duyệt tốt nhất thế giới IE (để tải các trình duyệt khác) thì rất tiếc, không có tên nào biết Promise là gì.

5. Thông tin thêm:

Ngoài phần lí thuyết giới thiệu phía trên ra, tức là phần bạn hay dùng nhất, thì Promise còn có nhiều functions khác, đặc biệt là Promise.all(arrayOf Promise or PromiseResult or PromiseError), và cái hay nhất là await. Bạn có thể tham khảo thêm tại trang hướng dẫn của Mozilla. Chúc các bạn phát hiện nhiều điều thú vị và vận dụng Promise theo cách hay nhất có thể.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.