Javascript hứa hẹn sự tò mò


96

Khi tôi gọi lời hứa này, đầu ra không khớp với chuỗi lệnh gọi hàm. Điều .thenđến trước .catch, mặc dù lời hứa với .thenđã được gọi sau. lý do cho điều đó là gì?

const verifier = (a, b) =>
  new Promise((resolve, reject) => (a > b ? resolve(true) : reject(false)));

verifier(3, 4)
  .then((response) => console.log("response: ", response))
  .catch((error) => console.log("error: ", error));

verifier(5, 4)
  .then((response) => console.log("response: ", response))
  .catch((error) => console.log("error: ", error));

đầu ra

node promises.js
response: true
error: false

34
Bạn không bao giờ nên dựa vào thời gian giữa các chuỗi lời hứa độc lập.
Bergi

Câu trả lời:


136

Đây là một loại câu hỏi thú vị để tìm hiểu sâu hơn.

Khi bạn làm điều này:

verifier(3,4).then(...)

trả về một lời hứa mới yêu cầu một chu trình khác quay lại vòng lặp sự kiện trước khi lời hứa mới bị từ chối đó có thể chạy .catch()trình xử lý sau đó. Chu kỳ bổ sung đó cho chuỗi tiếp theo:

verifier(5,4).then(...)

cơ hội để chạy .then()trình xử lý của nó trước dòng trước đó .catch()vì nó đã ở trong hàng đợi trước khi .catch()trình xử lý từ dòng đầu tiên có trong hàng đợi và các mục được chạy từ hàng đợi theo thứ tự FIFO.


Lưu ý rằng nếu bạn sử dụng .then(f1, f2)biểu mẫu thay cho biểu mẫu .then().catch(), nó sẽ chạy khi bạn mong đợi vì không có lời hứa bổ sung và do đó không có thêm đánh dấu nào liên quan:

const verifier = (a, b) =>
  new Promise((resolve, reject) => (a > b ? resolve(true) : reject(false)));

verifier(3, 4)
  .then((response) => console.log("response (3,4): ", response),
        (error) => console.log("error (3,4): ", error)
  );

verifier(5, 4)
  .then((response) => console.log("response (5,4): ", response))
  .catch((error) => console.log("error (5,4): ", error));

Lưu ý, tôi cũng gắn nhãn tất cả các tin nhắn để bạn có thể xem verifier()chúng đến từ cuộc gọi nào, giúp bạn dễ dàng đọc kết quả hơn rất nhiều.


Thông số kỹ thuật của ES6 về thứ tự gọi lại lời hứa và giải thích chi tiết hơn

Thông số ES6 cho chúng ta biết rằng "công việc" của lời hứa (khi nó gọi một cuộc gọi lại từ a .then()hoặc .catch()) được chạy theo thứ tự FIFO dựa trên thời điểm chúng được chèn vào hàng đợi công việc. Nó không đặt tên cụ thể là FIFO, nhưng nó chỉ định rằng các công việc mới được chèn vào cuối hàng đợi và các công việc được chạy từ đầu hàng đợi. Điều đó thực hiện đặt hàng FIFO.

PerformPromiseThen (thực thi lệnh gọi lại từ .then()) sẽ dẫn đến EnqueueJob , đó là cách trình xử lý giải quyết hoặc từ chối được lên lịch chạy thực sự. EnqueueJob chỉ định rằng công việc đang chờ xử lý được thêm vào phía sau hàng đợi công việc. Sau đó, hoạt động NextJob kéo mục từ phía trước hàng đợi. Điều này đảm bảo thứ tự FIFO trong việc phục vụ các công việc từ hàng đợi công việc Promise.

Vì vậy, trong ví dụ trong câu hỏi ban đầu, chúng ta nhận được các lệnh gọi lại cho verifier(3,4)lời hứa và verifier(5,4)lời hứa được chèn vào hàng đợi công việc theo thứ tự chúng được chạy vì cả hai lời hứa ban đầu đó đều được thực hiện. Sau đó, khi trình thông dịch quay trở lại vòng lặp sự kiện, đầu tiên nó sẽ nhận verifier(3,4)công việc. Lời hứa đó bị từ chối và không có cuộc gọi lại nào cho điều đó trong verifier(3,4).then(...). Vì vậy, những gì nó làm là từ chối lời hứa đã verifier(3,4).then(...)trả về và điều đó khiến verifier(3,4).then(...).catch(...)trình xử lý được chèn vào jobQueue.

Sau đó, nó quay trở lại vòng lặp sự kiện và công việc tiếp theo mà nó kéo từ jobQueue là verifier(5, 4)công việc. Điều đó có một lời hứa đã được giải quyết và một trình xử lý giải quyết nên nó gọi trình xử lý đó. Điều này làm cho response (5,4):đầu ra được hiển thị.

Sau đó, nó quay trở lại vòng lặp sự kiện và công việc tiếp theo mà nó kéo từ jobQueue là verifier(3,4).then(...).catch(...)công việc mà nó chạy điều đó và điều này khiến error (3,4)đầu ra được hiển thị.

Đó là bởi vì .catch()chuỗi thứ nhất nằm sâu hơn một mức hứa hẹn trong chuỗi của nó so với .then()chuỗi thứ hai gây ra thứ tự mà bạn đã báo cáo. Và, đó là bởi vì chuỗi lời hứa được chuyển từ cấp độ này sang cấp độ tiếp theo thông qua hàng đợi công việc theo thứ tự FIFO, không đồng bộ.


Khuyến nghị chung về việc dựa vào chi tiết lập lịch biểu này

FYI, nói chung, tôi cố gắng viết mã không phụ thuộc vào mức kiến ​​thức thời gian chi tiết này. Mặc dù nó gây tò mò và đôi khi hữu ích để hiểu, nhưng nó là mã mỏng manh vì một thay đổi đơn giản tưởng như vô hại đối với mã có thể dẫn đến sự thay đổi về thời gian tương đối. Vì vậy, nếu thời gian là quan trọng giữa hai chuỗi như thế này, thì tôi thà viết mã theo cách buộc thời gian theo cách tôi muốn hơn là dựa vào mức độ hiểu biết chi tiết này.


Cụ thể hơn, hành vi chính xác này không được ghi lại ở bất kỳ đâu trong đặc tả của các lời hứa, điều này làm cho điều này trở thành chi tiết triển khai. Bạn có thể nhận được hành vi khác nhau giữa các trình thông dịch (ví dụ: Node.js so với Edge và Firefox) hoặc giữa các phiên bản của trình thông dịch (ví dụ: Nút 12 so với Nút 14). Thông số chỉ đơn thuần nói rằng các lời hứa được xử lý không đồng bộ để tránh mã zalgo (IMHO đã hiểu nhầm BTW vì nó được thúc đẩy bởi những người đặt câu hỏi như thế này muốn phụ thuộc vào thời gian của mã có khả năng không đồng bộ)
slbetman

@slebetman - Không có tài liệu nào cho thấy rằng lệnh gọi lại lời hứa từ các lời hứa riêng biệt được gọi là FIFO dựa trên thời điểm chúng được chèn vào hàng đợi và không thể chạy cho đến lần đánh dấu tiếp theo? Có vẻ như đặt hàng FIFO là tất cả những gì được yêu cầu ở đây vì .then()phải trả lại một lời hứa mới mà bản thân nó phải giải quyết / từ chối không đồng bộ vào một lần đánh dấu trong tương lai, đó là điều dẫn đến việc đặt hàng này. Bạn có biết bất kỳ triển khai nào không sử dụng thứ tự FIFO của các lệnh gọi lại cạnh tranh không?
jfriend00

3
@slebetman Promises / A + không chỉ rõ điều đó. ES6 không chỉ định nó. (ES11 đã thay đổi hành vi của await, mặc dù).
Bergi

Từ thông số ES6 về thứ tự xếp hàng. PerformPromiseThensẽ dẫn đến EnqueueJobđó là cách mà trình xử lý giải quyết hoặc từ chối được lên lịch được gọi. EnqueueJob chỉ định rằng công việc đang chờ xử lý được thêm vào phía sau hàng đợi công việc. Sau đó, hoạt động NextJob kéo mục từ phía trước hàng đợi. Điều này đảm bảo thứ tự FIFO trong hàng đợi công việc Promise.
jfriend00

@Bergi Thay đổi này awaittrong ES11 là gì? Một liên kết là đủ. Cảm ơn!!
Pedro A

49

Promise.resolve()
  .then(() => console.log('a1'))
  .then(() => console.log('a2'))
  .then(() => console.log('a3'))
Promise.resolve()
  .then(() => console.log('b1'))
  .then(() => console.log('b2'))
  .then(() => console.log('b3'))

Thay vì đầu ra a1, a2, a3, b1, b2, b3, bạn sẽ thấy a1, b1, a2, b2, a3, b3 vì cùng một lý do - mỗi sau đó trả về một lời hứa và nó đi đến cuối vòng lặp sự kiện xếp hàng. Vì vậy, chúng ta có thể thấy "cuộc đua hứa hẹn" này. Tương tự khi có một số lời hứa lồng vào nhau.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.