Xin chào, nếu đã từng lập trình với Javascript, hẳn bạn đã có đôi lần nghe nói / sử dụng callback. Và với sự phát triển như hiện nay của Javascript, thì có một vấn đề cực kỳ nhức nhối đã được thể hiện với callback của Javascript, đó là [callback hell](http://callbackhell.com/). Dưới đây là ví dụ (yaoming) ![callbackhell](https://cdn-images-1.medium.com/max/800/1*3lEILqKvoasyVwpdlfVvbw.png) Và để giải quyết vấn đề này, có một lời hứa đã được hiện thực hóa, vâng tên của nó chính là *Promise*. Trong bài dịch lần này từ SitePoint (https://www.sitepoint.com/overview-javascript-promises/), mình sẽ giới thiệu Promise và tác dụng của nó trong việc giải quyết đống trên kia :D Đây thực sự là một tin tốt lành, giống như là quà Giáng Sinh dành cho các Javascript Developers vậy. Bạn sẽ cảm thấy thật hạnh phúc khi biết rằng `Promise` giờ đã trở thành một trong những thư viện chuẩn của JavaScript. Chrome 32 bản beta đã implement các API Promise basic. Tư tưởng của Promise không hề mới với developer. Hầu hết chúng ta ddax từng dùng `Promise` trong một vài thư viện đã có của JS như là Q, when, RSVP, Bluebird,... Ngay cả jQuery cũng có một chức năng gọi là [Deferred Object](https://api.jquery.com/category/deferred-object/) hoạt động tương tự như Promise. Nhưng việc `Promise` được support native thật đáng kinh ngạc. Và bài viết dưới đây sẽ miêu tả những điểm cơ bản của `Promise` và cách để đưa code JS của bạn lên một tầm cao mới :D # Overview Một object `Promise` đại diện cho một giá trị ở thời điểm hiện tại có thể chưa tồn tại, nhưng sẽ được xử lý và có giá trị vào một thời gian nào đó trong tương lai. Việc này sẽ giúp bạn viết các dòng code xử lý không đồng bộ trông có vẻ đồng bộ hơn. Lấy ví dụ, nếu bạn sử dụng Promise cho việc gọi API để lấy dữ liệu, bạn sẽ tạo ra một đối tượng `Promise` đại diện cho data lấy được từ API. Điều cốt lõi ở đây là dữ liệu sẽ chưa tồn tại ở thời điểm đối tượng Promise được tạo ra, mà chỉ có thể truy cập sau khi có response từ web service. Trong thời gian chờ lấy dữ liệu, `Promise` object sẽ đóng vai trò như một proxy cho dữ liệu. Hơn nữa, bạn có thể đính các callback vào `Promise` object để thực hiện việc xử lý dữ liệu. Các callback này sẽ chỉ thực hiện khi dữ liệu đã sẵn sàng. # Promise API Tiếp theo đây chúng ta sẽ đến với các API cơ bản của `Promise` Để bắt đầu, hãy cùng tìm hiểu đoạn code sau, đây là cách khởi tạo một đối tượng `Promise` . ```javascript if (window.Promise) { // Check if the browser supports Promises var promise = new Promise(function(resolve, reject) { //asynchronous code goes here }); } ``` Chúng ta bắt đầu với việc khởi tạo một đối tượng `Promise` và truyền vào đó một hàm callback. Hàm callback này sẽ nhận 2 tham số là `resolve` và `reject`, với cả 2 tham số đều có kiểu là hàm (function). Tất cả các code bất đồng bộ sẽ nằm trong hàm callback này. Nếu mọi thứ thành công, Promise sẽ được hoàn thành và hàm `resolve` sẽ được gọi. Trong trường hợp có lỗi, hàm `reject` sẽ được gọi với một đối tượng `Error`, nhằm xác định rằng Promise này đã bị không thành công. Giờ hãy thử xây dựng một ví dụ đơn giản để mô tả cách sử dụng `Promise`. Các dòng code sau sẽ tạo một request bất đồng bộ (asynchronous) đến một web service và trả về một đoạn JSON. Chúng ta sẽ tìm hiểu `Promise` hoạt động trong trường hợp này như thế nào ```javascript if (window.Promise) { console.log('Promise found'); var promise = new Promise(function(resolve, reject) { var request = new XMLHttpRequest(); request.open('GET', 'http://api.icndb.com/jokes/random'); request.onload = function() { if (request.status == 200) { resolve(request.response); // we got data here, so resolve the Promise } else { reject(Error(request.statusText)); // status is not 200 OK, so reject } }; request.onerror = function() { reject(Error('Error fetching data.')); // error occurred, reject the Promise }; request.send(); //send the request }); console.log('Asynchronous request made.'); promise.then(function(data) { console.log('Got data! Promise fulfilled.'); document.getElementsByTagName('body')[0].textContent = JSON.parse(data).value.joke; }, function(error) { console.log('Promise rejected.'); console.log(error.message); }); } else { console.log('Promise not available'); } ``` Ở đoạn code phía trên, phần khởi tạo `Promise` chứa các code bất đồng bộ dùng cho việc lấy dữ liệu từ web service thông qua API. Ở đây, chúng ta tạo ra một Ajax request đến đường link `http://api.icndb.com/jokes/random`, link này sẽ trả về một câu chuyện cười bất kỳ. Khi có response trả về (JSON string), nội dung của response sẽ được truyền và xử lý bởi hàm `resolve()`. Nếu có bất kỳ lỗi nào xảy ra, hàm `reject()` sẽ được gọi với input là đối tượng `Error`. Khi chúng ta khởi tạo một đối tượng `Promise`, chúng ta có một proxy đến dữ liệu sẽ sẵn sàng sử dụng trong tương lai. Trong trường hợp cụ thể ở trên, chúng ta hy vọng rằng dữ liệu sẽ được truyền về từ phía web service vào một thời điểm nào đó. Vậy làm thế nào để chúng ta biết rằng khi nào thì dữ liệu sẽ sẵn sàng cho việc sử dụng? Đây là việc mà `Promise.then()` sẽ giải quyết cho chúng ta. Hàm `then` sẽ dùng 2 tham số với ý nghĩa: một callback thành công và một callback thất bại. Các callback này sẽ được gọi khi `Promise` được xử lý xong xuôi (thành công hay thất bại). Nếu `Promise` được xử lý trơn tru, callback thành công sẽ được gọi với dữ liệu truyền vào hàm `resolve()`. Nếu promise gặp lỗi, callback thất bại được goi. Bất kể thứ gì bạn truyền vào `reject()` sẽ được truyền như mội tham số đến callback này. Code ở trên được demo ở [đây](http://plnkr.co/edit/ilf9xtDqrimWxZd77yLI?p=preview). Bạn có thể refresh lại page để mỗi lần nhìn thấy một câu chuyện cười khác nhau. Đồng thời hãy thử bật console của trình duyệt, từ đó bạn sẽ thấy thứ tự thực hiện code. Lưu ý rằng một `promise` sẽ có 3 trạng thái : * Pending (đang xử lý) * Fulfilled (đã hoàn thành) * Rejected (đã bị từ chối) Mỗi đối tượng `Promise` sẽ có một thuộc tính private chứa trạng thái hiện tại của `Promise`. Khi một `Promise` được hoàn thành hay từ chối, giá trị của thuộc tính này sẽ ngay lập tức được cập nhật với trạng thái của `Promise`. Điều này có nghĩa là một `Promise` chỉ có thể thành công hoặc thất bại một lần duy nhất. Nếu một `Promise` đã được hoành thành, và sau đó bạn mới gọi hàm `then()` của promise và truyền vào 2 callback, thì callback thành công sẽ luôn được gọi. Vì thế, trong thế giới của `Promise`, chúng ta không quan đến việc khi nào `Promise` được xử lý. Chúng ta chỉ quan tâm đến kết quả đầu ra của `Promise`. # Nối nhiều Promise Sẽ có nhiều chúng ta muốn nối các promises với nhau. Ví dụ, bạn có thể có nhiều thao tác bất đồng bộ cần xử lý. Khi một thao tác trả về dữ liệu, bạn sẽ bắt đầu một xử lý bất đồng bộ khác sử dụng một phần dữ liệu từ thao tác trước đó, và cứ tiếp tục như vậy. `Promise` hỗ trợ việc nối các `Promise` với nhau giống như ví dụ dưới đây ```javascript function getPromise(url) { // trả về một Promise ở ddây // gửi một request lấy dữ liệu từ một url (request bất đồng bộ) // sau khi lấy về kết quả, xử lý promise với dữ liệu nhận được } var promise = getPromise('some url here'); promise.then(function(result) { //chúng ta có dữ liệu của url 'some url here' ở đây return getPromise(result); //và trả về một promise khác }).then(function(result) { //ở đây chứa kết quả promise vừa trả về ở trên và logic để xử lý dữ liệu cuối cùng }); ``` Có một điều đáng lưu ý ở đây là khi một giá trị thông thường được trả về trong hàm `then()` đầu tiên, hàm `then()` thứ hai sẽ thực hiện với giá trị đó. Nhưng nếu bạn trả về 1 `Promise` ở hàm `then()` thứ nhất, thì hàm `then()` thứ hai sẽ chờ và chỉ được gọi cho đến khi `Promise` ở hàm `then()` thứ nhất thực hiện xong xuôi. # Xử lý lỗi Trước đó tôi đã giới thiệu hàm `then()` sẽ nhận 2 hàm callbacks làm tham số. Hàm callback thứ 2 sẽ được gọi khi `Promise` bị từ chối. Tuy nhiên chúng ta cũng có một cách khác, sử dụng một hàm tên là `catch()`, chúng ta có thể xử lý khi `Promise` bị từ chối. Đoạn code dưới đây mô tả việc sử dụng hàm `catch()`: ```javascript promise.then(function(result) { console.log('Got data!', result); }).catch(function(error) { console.log('Error occurred!', error); }); ``` Đoạn ở trên tương đương với : ```javascript promise.then(function(result) { console.log('Got data!', result); }).then(undefined, function(error) { console.log('Error occurred!', error); }); ``` Lưu ý rằng nếu Promise bị từ chối và hàm `then()` không có callback để xử lý việc này, xử lý sẽ được chuyển đến hàm xử lý lỗi của hàm `then()` tiếp theo hoặc hàm `catch()` tiếp theo. Ngoài việc dùng để xử lý Promise bị lỗi, hàm callback trong `catch()` cũng được gọi khi có bất kỳ exception nào được bắn ra từ callback của hàm khởi tạo `Promise`. Vì thế bạn có thể dùng `catch()` cho việc lưu log. Chú ý rằng chúng ta có thể sử dụng `try...catch` để xử lý lỗi, nhưng điều đó là không cần thiết khi sử dụng `Promise` vì bất kỳ exception nào cũng luôn được xử lý bởi hàm `catch()`. # Tổng kết Trên đây chỉ là những giới thiệu cơ bản về API Promise của Javascript. Khi nắm vững nó, bạn có thể viết code xử lý các thao tác bất đồng bộ một cách dễ dàng. Chúng ta có thể xử lý như bình thường mà không cần quan tâm giá trị sẽ được trả về sau đó trong tương lai. Có một vài API về `Promise` chưa được liệt kê ở đây. Để tìm hiểu thêm, các bạn có thể tham khảo một số tài liệu sau. * [HTML5Rocks](http://www.html5rocks.com/en/tutorials/es6/promises/) * [Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Promise)