Hiểu về promises trong JavaScript
Bài đăng này đã không được cập nhật trong 4 năm

Mở đầu
Javascript là một ngôn ngữ lập trình đơn luồng, có nghĩa là chỉ có thể có một điều có thế xảy ra tại một không đồng bộ (asynchronous) ví dụ như netword request.
Sử dụng Promise chúng ta có thể tránh được callback hell khét tiếng và làm cho code  trông sạch hơn dễ đọc và dễ hiểu hơn.
Giả sử chúng ta đang muốn lấy dữ liệu một cách không đồng bộ bằng cách sử dụng callback như sau:
getData (function (x) { 
    console.log (x); 
    getMoreData (x, function (y) { 
        console.log (y); 
        getSomeMoreData (y, function (z) { 
            console.log (z); 
        }); 
    } ); 
});
Ở đây tôi đang yêu cầu một số dữ liệu từ server bằng cách gọi hàm getData() , hàm này sẽ nhận dữ liệu bên trong hàm callback. Bên trong hàm callback tôi đang yêu cầu thêm một số dữ liệu bằng hàm getMoreData. Hàm nhận dữ liệu là kết quả trả về của hàm trước đó.
Đây chính xác là những gì chúng ta gọi là callback hell. Nơi mỗi callback lại được lồng trong 1 callback khác và mỗi callback lại phụ thuộc vào cha mẹ nó.
Ta có thể viết lại đoạn code trên như sau:
getData () 
  .then ((x) => { 
    console.log (x); 
    return getMoreData (x); 
  }) 
  .then ((y) => { 
    console.log (y); 
    return getSomeMoreData (y); 
  } ) 
  .then ((z) => { 
    console.log (z); 
   });
Ta thấy ví dụ này rõ ràng và dễ hiểu hơn hẳn ví dụ sử dụng callback ở trước đó.
What is a Promise ?
Promise là một Object chứa giá trị tương lai của hoạt động không đồng bộ. Ví dụ: Nếu ta đang yêu cầu một số dữ liệu từ server, promise hứa với chúng ta sẽ lấy ra dữ liệu chúng ta có thể sử dụng trong tương lai.
Trước khi vào khi đi vào nội dung kĩ thuật, chúng ta sẽ đi tìm thuật ngữ promise.
States of a Promise
Promise cũng giống như lời hứa trong thực tế có 3 trạng thái: chưa được giải quyết (unresolved), đã giải quyết (resolved) ,  hoặc bị từ chối (rejected). Ví dụ:

- Unresolved or Pending - Promiseđang chờ xử lý nếu kết quả chưa sẵn sàng. Khi đó, nó đang chờ một thứ gì đó kết thúc (Ví dụ hoạt động bất đồng bộ).
- Resolved or Fulfilled - Promiseđược giải quyết nếu có kết quả. Đó là một cái gì đó đã hoàn thành (ví dụ: hoạt động không đồng bộ) và tất cả đều diễn ra tốt đẹp.
- Rejected - Promisebị từ chối nếu xảy ra lỗi. Bây giờ chúng ta đã biết Promise là gì và thuật ngữPromise, hãy quay trở lại khía cạnh thực tế củaPromise.
Creating a Promise
Hầu hết, các bạn sẽ sử dụng những promise hơn là tạo ra chúng, nhưng điều quan trọng vẫn là bạn phải biết cách tạo ra chúng.
Syntax:
const promise = new Promise((resolve, reject) => {
    ...
  });
Chúng tôi tạo một Promise mới bằng cách khởi tạo hàm khởi tạo promise, nó nhận một đối số duy nhất là một hàm callback. Đối số này  còn được gọi là hàm thực thi nhận hai callback là resolve và reject.
Hàm thực thi được thực thi ngay lập tức khi một promise được tạo. Promise được giải quyết bằng cách gọi resolve() và từ chối bằng cách gọi reject().
Ví dụ:
const promise = new Promise((resolve, reject) => {
  if(allWentWell) {
    resolve('All things went well!');
  } else {
    reject('Something went wrong');
  }
});
resolve() và reject() nhận một đối số có thể là một string, number, boolean, array hoặc object.
Hãy xem một ví dụ khác để hiểu đầy đủ về việc tạo Promise.
const promise = new Promise((resolve, reject) => {
  const randomNumber = Math.random();
  setTimeout(() => {
    if(randomNumber < 6) {
      resolve('All things went well!');
    } else {
    reject('Something went wrong');
  }
  }, 2000);
});
Ở đây tôi đang tạo một lời promise bằng cách sử dụng phương thức khởi tạo Promise. Promise được giải quyết hoặc bị từ chối sau 2 giây kể từ khi tạo. Promise được giải quyết nếu randomNumber nhỏ hơn 6 và bị từ chối nếu không.
Khi Promise được tạo, nó sẽ ở trạng thái chờ xử lý và giá trị của nó sẽ là undefined. Ví dụ:

Sau khi bộ đếm thời gian 2 giây kết thúc, promise được giải quyết hoặc bị từ chối một cách ngẫu nhiên và giá trị của nó sẽ là giá trị được chuyển đến chức năng  resolved hoặc reject. Ví dụ:
Lưu ý  - Lời hứa chỉ có thể được giải quyết hoặc bị từ chối một lần. Việc gọi thêm resolve() hoặc reject() không có tác dụng đối với trạng thái  Promise. Ví dụ:
const promise = new Promise((resolve, reject) => {
  resolve('Promise resolved');  // Promise is resolved
  reject('Promise rejected');   // Promise can't be rejected
});
Vì resolve() được gọi trước nên promise sẽ được giải quyết. Việc gọi reject() sau đó sẽ không ảnh hưởng đến trạng thái Promise.
Consuming a Promise
Bây giờ chúng ta đã biết cách tạo một Promise, hãy hiểu cách sử dụng một Promise đã được tạo. Chúng tôi sử dụng một  Promise bằng cách gọi phương thức then() và catch()  trên Promise.
Ví dụ: Yêu cầu dữ liệu từ một API bằng cách sử dụng Promise.
.then() Syntax :promise.then(successCallback, failureCallback)
successCallbackđược được gọi khi một promise đã được giải quyết. Nó nhận một đối số là giá trị được truyền tới resolve().
failureCallback được gọi khi một promise bị từ chối. Nó nhận một đối số là giá trị được truyền tới reject().
Ví dụ:

const promise = new Promise((resolve, reject) => {
  const randomNumber = Math.random();
  
  if(randomNumber < .7) {
    resolve('All things went well!');
  } else {
    reject(new Error('Something went wrong'));
  }
});
promise.then((data) => {
  console.log(data);  // prints 'All things went well!'
  },
  (error) => {
  console.log(error); // prints Error object
  }
);
Ở đây nếu promise được giải quyết, thì successCallback sẽ được gọi với giá trị được chuyển đến resolve(). Và nếu lời hứa bị từ chối, thì failureCallback được gọi với giá trị được chuyển đến reject().
.catch() Syntax :promise.catch(failureCallback)
Chúng tôi sử dụng catch() để xử lý lỗi. Nó dễ đọc hơn là xử lý các lỗi   failureCallback  bên trong then(). Ví dụ:
const Promise = new Promise ((giải quyết, từ chối) => {
  từ chối (Lỗi mới ('Đã xảy ra lỗi'));
});
hứa 
  .then ((data) => { 
     console.log (data); 
   }) 
  .catch ((error) => { 
     console.log (error); // in ra Error object 
  });
Promise Chaining
Các phương thức then() và catch() cũng có thể trả về một promise mới có thể được xử lý bằng cách xâu chuỗi then () khác vào cuối phương thức then() trước đó.
Chúng tôi sử dụng chuỗi lời hứa khi chúng tôi muốn giải quyết các promise theo một trình tự.
Ví dụ:
const promise1 = new Promise((resolve, reject) => {
  resolve('Promise1 resolved');
});
const promise2 = new Promise((resolve, reject) => {
  resolve('Promise2 resolved');
});
const promise3 = new Promise((resolve, reject) => {
  reject('Promise3 rejected');
});
promise1
  .then((data) => {
    console.log(data);  // Promise1 resolved
    return promise2;
  })
  .then((data) => {
    console.log(data);  // Promise2 resolved
    return promise3;
  })
  .then((data) => {
    console.log(data);
  })
  .catch((error) => {
    console.log(error);  // Promise3 rejected
  });
Vậy điều gì đang xảy ra ở đây?
- Khi promise1được giải quyết,then()phương thức được gọi là phương thức trả vềpromise2.
- Tiếp theo then()được gọi là khipromise2được giải quyết và trả vềpromise3.
- Kể từ khi promise3bị từ chối, tiếp theothen()không được gọi thay vào đócatch()được gọi là xử lýpromise3từ chối.
Lưu ý - Nói chung chỉ một catch()là đủ để xử lý việc từ chối bất kỳ promise nào trong chuỗi promise, nếu nó ở cuối chuỗi.
Common Mistake
Rất nhiều người mới bắt đầu mắc sai lầm khi lồng những lời hứa vào bên trong một lời promise.
Ví dụ:
const promise1 = new Promise((resolve, reject) => {
  resolve('Promise1 resolved');
});
const promise2 = new Promise((resolve, reject) => {
  resolve('Promise2 resolved');
});
const promise3 = new Promise((resolve, reject) => {
  reject('Promise3 rejected');
});
promise1.then((data) => {
  console.log(data);  // Promise1 resolved
  promise2.then((data) => {
    console.log(data);  // Promise2 resolved
    
    promise3.then((data) => {
      console.log(data);
    }).catch((error) => {
      console.log(error);  // Promise3 rejected
    });
  }).catch((error) => {
    console.log(error);
  })
}).catch((error) => {
    console.log(error);
  });
Mặc dù điều này hoạt động tốt, đây được coi là một phong cách kém sang và làm cho code của chúng tôi khó đọc. Nếu bạn có một chuỗi các promise cần giải quyết, tốt hơn là bạn nên xâu chuỗi các promise một cách lần lượt hơn là lồng chúng vào bên trong một chuỗi khác.
Promise.all ()
Phương thức này nhận một mảng các lời hứa làm đầu vào và trả về một lời hứa mới thực hiện khi tất cả các lời hứa bên trong mảng đầu vào đã hoàn thành hoặc từ chối ngay khi một trong các lời hứa trong mảng từ chối. Ví dụ:
const Promise1 = new Promise ((giải quyết, từ chối) => { 
setTimeout (() => { 
  giải quyết ('Promise1 đã giải quyết'); 
}, 2000); 
});
const Promise2 = new Promise ((giải quyết, từ chối) => { 
setTimeout (() => { 
  giải quyết ('Promise2 đã giải quyết'); 
}, 1500); 
});
Promise.all ([promise1, promise2]) .then 
  ((data) => console.log (data [0], data [1])) 
  .catch ((error) => console.log (error));
- Ở đây, đối số dữ liệu bên trong then()phương thức là một mảng chứa các giá trị hứa hẹn theo thứ tự như được định nghĩa trong mảng hứa hẹn được chuyển đếnPromise.all()(nếu tất cả cácPromiseđều thực hiện).
- Promisebị từ chối với lý do từ chối từ lời hứa bị từ chối đầu tiên trong mảng đầu vào. Ví dụ:
const promise1 = new Promise((resolve, reject) => {
 setTimeout(() => {
  resolve('Promise1 resolved');
 }, 2000);
});
const promise2 = new Promise((resolve, reject) => {
 setTimeout(() => {
  reject('Promise2 rejected');
 }, 1500);
});
Promise.all([promise1, promise2])
  .then((data) => console.log(data[0], data[1]))
  .catch((error) => console.log(error));  // Promise2 rejected
- Ở đây chúng tôi có promisehứa trong đó một lời hứa được giải quyết sau 2 giây và lời hứa kia bị từ chối sau 1,5 giây.
- Ngay sau khi promisethứ hai bị từ chối sau 1,5 giây, lời hứa được trả lại từPromise.all()sẽ bị từ chối mà không cần đợipromiseđầu tiên được giải quyết. Phương pháp này có thể hữu ích khi bạn có nhiều lời hứa và bạn muốn biết khi nàopromisecác lời hứa đã được giải quyết. Ví dụ: nếu bạn đang yêu cầu dữ liệu từ các API khác nhau và bạn chỉ muốn làm điều gì đó với dữ liệu khi tất cả các yêu cầu đều thành công.
Vì vậy, hãy Promise.all() đợi tất cả các promise thành công và thất bại nếu bất kỳ promise nào trong mảng không thành công.
Promise.race ()
Phương thức này nhận một mảng các promise làm đầu vào và trả về một promise mới thực hiện ngay khi một trong cácpromise trong mảng đầu vào thực hiện hoặc từ chối ngay khi một trong các promise trong mảng đầu vào từ chối. Ví dụ:
const promise1 = new Promise((resolve, reject) => {
 setTimeout(() => {
  resolve('Promise1 resolved');
 }, 1000);
});
const promise2 = new Promise((resolve, reject) => {
 setTimeout(() => {
  reject('Promise2 rejected');
 }, 1500);
});
Promise.race([promise1, promise2])
  .then((data) => console.log(data))  // Promise1 resolved
  .catch((error) => console.log(error));
- Ở đây chúng ta có hai promisetrong đó mộtpromiseđược giải quyết sau 1 giây vàpromisekia bị từ chối sau 1,5 giây.
- Ngay sau khi promiseđầu tiên được giải quyết sau 1 giây, lời hứa trả lại từPromise.race()sẽ được giải quyết mà không cần đợipromisethứ hai được giải quyết hoặc bị từ chối.
- Ở đây dữ liệu được truyền cho then()phương thức là giá trị củapromiseđầu tiên được giải quyết. Vì vậy, hãyPromise.race()đợi một trong nhữngpromisetrong mảng thành công hay thất bại và thực hiện hoặc từ chối ngay sau khi một trong nhữngpromisetrong mảng được giải quyết hoặc bị từ chối.
Phần kết luận
Chúng tôi đã tìm hiểu  promise là gì và cách sử dụng chúng trong JavaScript. Một promise có hai phần: 1) Tạo ra promise và 2) sử dụng một promise. Hầu hết các lần bạn sẽ sử dụng những promise hơn là tạo ra chúng, nhưng điều quan trọng vẫn là bạn phải biết cách tạo ra chúng.
Link tham khảo: https://blog.bitsrc.io/understanding-promises-in-javascript-c5248de9ff8f
All rights reserved
 
  
 