Lời hứa là gì?
Một lời hứa JavaScript là một đối tượng biểu thị việc hoàn thành hay thất bại của một tác vụ không đồng bộ và giá trị kết quả của nó.¹
Kết thúc.
Tôi đang đùa tất nhiên. Vì vậy, định nghĩa đó thậm chí có nghĩa là gì?
Trước hết, nhiều thứ trong JavaScript là các đối tượng. Bạn có thể tạo một đối tượng theo một vài cách khác nhau. Cách phổ biến nhất là với cú pháp nghĩa đen của đối tượng:
const myCar = {
color: 'blue',
type: 'sedan',
doors: '4',
};
Bạn cũng có thể tạo một class
và khởi tạo nó với new
từ khóa.
class Car {
constructor(color, type, doors) {
this.color = color;
this.type = type;
this.doors = doors
}
}
const myCar = new Car('blue', 'sedan', '4');
console.log(myCar);

Một lời hứa chỉ đơn giản là một đối tượng mà chúng ta tạo ra như ví dụ sau. Chúng tôi khởi tạo nó với new
từ khóa. Thay vì truyền ba tham số để tạo ô tô (màu sắc, loại và cửa), chúng ta truyền vào một hàm nhận hai đối số: resolve
và reject
.
Cuối cùng, các lời hứa cho chúng tôi biết điều gì đó về việc hoàn thành chức năng không đồng bộ mà chúng tôi đã trả lại từ đó–nếu nó hoạt động hay không. Chúng tôi nói rằng chức năng đã thành công bằng cách nói lời hứa giải quyếtvà không thành công bằng cách nói lời hứa vật bị loại bỏ.
const myPromise = new Promise(function(resolve, reject) {});
console.log(myPromise);

const myPromise = new Promise(function(resolve, reject) {
resolve(10);
});

Hãy xem, không quá đáng sợ – chỉ là một đối tượng chúng tôi đã tạo. Và, nếu chúng ta mở rộng nó ra một chút:

Ngoài ra, chúng tôi có thể chuyển bất cứ điều gì chúng tôi muốn vào giải quyết và từ chối. Ví dụ, chúng ta có thể truyền một đối tượng thay vì một chuỗi:
return new Promise((resolve, reject) => {
if(somethingSuccesfulHappened) {
const successObject = {
msg: 'Success',
data,//...some data we got back
}
resolve(successObject);
} else {
const errorObject = {
msg: 'An error occured',
error, //...some error we got back
}
reject(errorObject);
}
});
Hoặc, như chúng ta đã thấy trước đó, chúng ta không phải vượt qua bất cứ thứ gì:
return new Promise((resolve, reject) => {
if(somethingSuccesfulHappend) {
resolve()
} else {
reject();
}
});
Còn phần “không đồng bộ” của định nghĩa thì sao?
JavaScript là một luồng đơn. Điều này có nghĩa là nó chỉ có thể chạy một thứ tại một thời điểm. Nếu bạn có thể tưởng tượng về một con đường, bạn có thể coi JavaScript như một đường cao tốc một làn. Một số mã (mã không đồng bộ) có thể trượt qua vai để cho phép mã khác vượt qua nó. Khi mã không đồng bộ đó được thực hiện, nó sẽ quay trở lại đường.
Như một lưu ý phụ, chúng tôi có thể trả lại một lời hứa từ không tí nào chức năng. Nó không phải là không đồng bộ. Nói như vậy, các lời hứa thường được trả về trong trường hợp chức năng mà chúng trả về không đồng bộ. Ví dụ: một API có các phương thức lưu dữ liệu vào máy chủ sẽ là một ứng cử viên tuyệt vời để trả lại một lời hứa!
Mang đi:
Lời hứa cung cấp cho chúng tôi một cách để đợi mã không đồng bộ của chúng tôi hoàn thành, nắm bắt một số giá trị từ mã đó và chuyển các giá trị đó sang các phần khác trong chương trình của chúng tôi.
Tôi có một bài viết ở đây đi sâu hơn vào các khái niệm này: Thrown For a Loop: Hiểu về Vòng lặp và Thời gian chờ trong JavaScript.
Làm thế nào để chúng ta sử dụng một lời hứa?
Sử dụng một lời hứa cũng được gọi là tiêu thụ một lời hứa. Trong ví dụ trên, hàm của chúng tôi trả về một đối tượng lời hứa. Điều này cho phép chúng tôi sử dụng chuỗi phương thức với chức năng của chúng tôi.
Đây là một ví dụ về chuỗi phương thức mà tôi cá là bạn đã thấy:
const a="Some awesome string";
const b = a.toUpperCase().replace('ST', '').toLowerCase();
console.log(b); // some awesome ring
Bây giờ, hãy nhớ lại lời hứa (giả vờ) của chúng ta:
const somethingWasSuccesful = true;
function someAsynFunction() {
return new Promise((resolve, reject){
if (somethingWasSuccesful) {
resolve();
} else {
reject()
}
});
}
Và, sử dụng lời hứa của chúng tôi bằng cách sử dụng chuỗi phương thức:
someAsyncFunction
.then(runAFunctionIfItResolved(withTheResolvedValue))
.catch(orARunAfunctionIfItRejected(withTheRejectedValue));
Một (thêm) ví dụ thực tế.
Hãy tưởng tượng bạn có một hàm lấy người dùng từ cơ sở dữ liệu. Tôi đã viết một hàm ví dụ trên Codepen để mô phỏng một API mà bạn có thể sử dụng. Nó cung cấp hai tùy chọn để truy cập kết quả. Thứ nhất, bạn có thể cung cấp chức năng gọi lại nơi bạn có thể truy cập người dùng hoặc bất kỳ lỗi nào. Hoặc hai, hàm trả về một lời hứa như một cách để truy cập người dùng hoặc lỗi.
Theo truyền thống, chúng tôi sẽ truy cập kết quả của mã không đồng bộ thông qua việc sử dụng các cuộc gọi lại.
rr someDatabaseThing(maybeAnID, function(err, result)) {
//...Once we get back the thing from the database...
if(err) {
doSomethingWithTheError(error)
} else {
doSomethingWithResults(results);
}
}
Việc sử dụng các cuộc gọi lại là Vâng cho đến khi chúng trở nên quá lồng vào nhau. Nói cách khác, bạn phải chạy nhiều mã không đồng bộ hơn với mỗi kết quả mới. Kiểu gọi lại này trong các cuộc gọi lại có thể dẫn đến một thứ được gọi là “địa ngục gọi lại”.

Lời hứa cung cấp cho chúng tôi một cách thanh lịch và dễ đọc hơn để xem luồng chương trình của chúng tôi.
doSomething()
.then(doSomethingElse) // and if you wouldn't mind
.catch(anyErrorsPlease);
Viết lời hứa của chính chúng ta: Goldilocks, Ba con gấu và Siêu máy tính
Hãy tưởng tượng bạn tìm thấy một bát súp. Bạn muốn biết nhiệt độ của món súp đó trước khi ăn. Bạn không có nhiệt kế, nhưng may mắn thay, bạn có quyền truy cập vào một siêu máy tính cho bạn biết nhiệt độ của bát súp. Thật không may, siêu máy tính này có thể mất tới 10 giây để nhận được kết quả.

Dưới đây là một vài điều cần chú ý.
- Chúng tôi bắt đầu một biến toàn cục được gọi là
result
. - Chúng tôi mô phỏng thời gian trễ mạng với
Math.random()
vàsetTimeout()
. - Chúng tôi mô phỏng nhiệt độ với
Math.random()
. - Chúng tôi giới hạn các giá trị độ trễ và nhiệt độ trong một phạm vi bằng cách thêm một số “toán học” bổ sung. phạm vi cho
temp
là 1 ăn 300; phạm vi chodelay
là 1000 mili giây đến 10000 mili giây (1 giây đến 10 giây). - Chúng tôi ghi nhật ký độ trễ và nhiệt độ để có ý tưởng về thời gian thực hiện chức năng này và kết quả mà chúng tôi mong đợi sẽ thấy khi hoàn thành.
Chạy chức năng và ghi kết quả.
getTemperature();
console.log(results); // undefined
Nhiệt độ không xác định. Chuyện gì đã xảy ra thế?
Chức năng sẽ mất một khoảng thời gian nhất định để chạy. Biến không được đặt cho đến khi độ trễ kết thúc. Vì vậy, trong khi chúng tôi chạy chức năng, setTimeout
là không đồng bộ. Phần mã trong setTimeout
di chuyển ra khỏi luồng chính vào khu vực chờ.
Tôi có một bài viết ở đây đi sâu hơn vào quy trình này: Thrown For a Loop: Hiểu về Vòng lặp và Thời gian chờ trong JavaScript.
Vì một phần của chức năng của chúng tôi đặt biến result
di chuyển vào vùng lưu trữ cho đến khi hoàn thành, trình phân tích cú pháp của chúng tôi có thể tự do chuyển sang dòng tiếp theo. Trong trường hợp của chúng tôi, đó là của chúng tôi console.log()
. Tại thời điểm này, result
vẫn chưa được xác định vì chúng tôi setTimeout
nó chưa phải là kết thúc.
Vì vậy, những gì khác chúng ta có thể thử? chúng ta có thể chạy getTemperature()
và sau đó đợi 11 giây (vì độ trễ tối đa của chúng tôi là mười giây) và sau đó console.log kết quả.
getTemperature();
setTimeout(() => {
console.log(result);
}, 11000);
// Too Hot | Delay: 3323 | Temperature: 209 deg
Điều này hoạt động, nhưng vấn đề với kỹ thuật này là, mặc dù trong ví dụ của chúng tôi, chúng tôi biết độ trễ mạng tối đa, nhưng trong một ví dụ thực tế, đôi khi có thể mất hơn mười giây. Và, ngay cả khi chúng tôi có thể đảm bảo độ trễ tối đa là mười giây, nếu kết quả sẵn sàng sớm hơn, thì chúng tôi đang lãng phí thời gian.
Lời hứa giải cứu
Chúng tôi sẽ cấu trúc lại getTemperature()
chức năng để trả lại một lời hứa. Và thay vì đặt kết quả, chúng tôi sẽ từ chối lời hứa trừ khi kết quả là “vừa phải”, trong trường hợp đó chúng tôi sẽ giải quyết lời hứa. Trong cả hai trường hợp, chúng tôi sẽ chuyển một số giá trị để giải quyết và từ chối.

Bây giờ chúng ta có thể sử dụng kết quả của lời hứa mà chúng ta đang trả lại (còn được gọi là tiêu thụ lời hứa).
getTemperature()
.then(result => console.log(result))
.catch(error => console.log(error));
// Reject: Too Cold | Delay: 7880 | Temperature: 43 deg
.then
sẽ được gọi khi lời hứa của chúng tôi được giải quyết và sẽ trả lại bất kỳ thông tin nào chúng tôi chuyển vào resolve
.
.catch
sẽ được gọi khi lời hứa của chúng tôi từ chối và sẽ trả lại bất kỳ thông tin nào chúng tôi chuyển vào reject
.
Rất có thể, bạn sẽ tiêu thụ nhiều lời hứa hơn là tạo ra chúng. Trong cả hai trường hợp, chúng giúp làm cho mã của chúng ta thanh lịch hơn, dễ đọc hơn và hiệu quả hơn.
Tóm lược
- Lời hứa là các đối tượng chứa thông tin về việc hoàn thành một số mã không đồng bộ và bất kỳ giá trị kết quả nào mà chúng tôi muốn chuyển vào.
- Để trả lại một lời hứa, chúng tôi sử dụng
return new Promise((resolve, reject)=> {})
- Để tiêu thụ một lời hứa, chúng tôi sử dụng
.then
để lấy thông tin từ một lời hứa đã được giải quyết và.catch
để lấy thông tin từ một lời hứa đã bị từ chối. - Bạn có thể sẽ sử dụng (tiêu thụ) những lời hứa nhiều hơn những gì bạn sẽ viết.
Người giới thiệu
1.) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise