Tại sao javascript ES6 Promises tiếp tục thực thi sau khi giải quyết?


97

Theo tôi hiểu thì một lời hứa là thứ có thể giải quyết () hoặc từ chối () nhưng tôi đã rất ngạc nhiên khi phát hiện ra rằng mã trong lời hứa vẫn tiếp tục thực thi sau khi một giải quyết hoặc từ chối được gọi.

Tôi coi giải quyết hoặc từ chối là một phiên bản thoát hoặc trả lại không thân thiện với đồng bộ, điều này sẽ tạm dừng tất cả việc thực thi chức năng ngay lập tức.

Ai đó có thể giải thích suy nghĩ đằng sau lý do tại sao ví dụ sau đôi khi hiển thị console.log sau một cuộc gọi giải quyết:

var call = function() {
    return new Promise(function(resolve, reject) {
        resolve();
        console.log("Doing more stuff, should not be visible after a resolve!");
    });
};

call().then(function() {
    console.log("resolved");
});

jsbin


12
Câu hỏi hợp lý, nhưng một lần nữa, JS chỉ thực hiện hết câu lệnh này đến câu lệnh khác giống như bạn nói với nó. resolve()không phải là một câu lệnh điều khiển JS có tác dụng một cách kỳ diệu return, nó chỉ là một lời gọi hàm và vâng, việc thực thi tiếp tục sau đó.

Đây là một câu hỏi hay, và thậm chí sau khi đọc tất cả các câu trả lời, tôi không chắc chắn về các thực hành tốt nhất ...
Gabriel Glenn

Tôi nghĩ rằng sự hiểu lầm xuất phát từ chính xác những gì bạn đang kết thúc bằng giải quyết (): lời hứa được giải quyết ngay sau khi bạn gọi giải quyết (), nhưng như những người khác đã nói, điều này không có nghĩa là hàm đã kết thúc lời hứa đã chấm dứt nó cũng vậy, vì vậy nó tiếp tục cho đến khi kết thúc "bình thường".
Giuseppe Bertone

Câu trả lời:


143

JavaScript có khái niệm "chạy để hoàn thành" . Trừ khi một lỗi được tạo ra, một hàm được thực thi cho đến khi đạt đến một returncâu lệnh hoặc kết thúc của nó. Mã khác bên ngoài chức năng không thể can thiệp vào điều đó (trừ khi, một lần nữa, một lỗi được đưa ra).

Nếu bạn muốn resolve()thoát khỏi chức năng khởi tạo của mình, bạn phải thêm nó bằng cách return:

return new Promise(function(resolve, reject) {
    return resolve();
    console.log("Not doing more stuff after a return statement");
});

Xin chào Felix - tôi nghĩ rằng đây chỉ là một phần của câu chuyện - phần khác resolve()là bản thân nó là một hàm không đồng bộ. Như chúng ta đã thấy trong câu trả lời (đã xóa) khác, một số người tin rằng việc gọi resolvesẽ chạy ngay lập tức bất kỳ lệnh gọi lại nào.
Alnitak

3
resolveBản thân @Alnitak không phải là không đồng bộ, nó hoàn toàn đồng bộ. Mặc dù sử dụng API ES6 nghiêm ngặt, không thể quan sát được nó đồng bộ hay không đồng bộ.
Esailija

1
@Esailija ok, có lẽ tôi không rõ ràng. Một số người tin rằng việc gọi resolvesẽ dẫn đến bất kỳ lệnh gọi lại nào đã đăng ký được gọi ngay lập tức để chúng là một phần của ngăn xếp cuộc gọi hiện tại. Điều đó không đúng, thay vào đó nó chỉ xếp hàng các cuộc gọi lại (và bạn nói đúng, nó không phải là không đồng bộ, nhưng nó chỉ thực hiện nhiệm vụ của mình và chấm dứt ngay lập tức)
Alnitak

@Alnitak: Tôi hiểu bạn đang nói gì. Tôi chỉ giải thích nó là tại sao nó console.loghiển thị thay vì tại sao nó lại hiển thị theo thứ tự đó. Cho đến nay, những gì resolvehứa hẹn và như thế nào không liên quan đến cách tôi giải thích câu hỏi. Nhưng tất nhiên điều quan trọng là phải biết trong bối cảnh của những lời hứa. Một trong những lý do tôi ủng hộ câu trả lời của bạn :)
Felix Kling.

9
@Bergi, trong chỉnh sửa của bạn, bạn nói "return Resolution ();" mà có vẻ không bình thường. Để thuyết phục bản thân rằng không có gì quan trọng xảy ra ở đó, tôi đã phải đọc tài liệu và thấy rằng (1) giải quyết () dường như không trả về bất kỳ kết quả nào và (2) giá trị trả về của lệnh gọi lại khởi tạo không dường như được sử dụng. Sẽ không rõ ràng hơn nếu nói "Resolution (); return;" từ đó tránh được sự phân tâm này?
Don Hatch

19

Các lệnh gọi lại sẽ được gọi khi bạn resolvemột lời hứa vẫn được đặc tả yêu cầu gọi là không đồng bộ. Điều này là để đảm bảo hành vi nhất quán khi sử dụng các lời hứa cho sự kết hợp của các hành động đồng bộ và không đồng bộ.

Do đó, khi bạn gọi resolvelệnh gọi lại được xếp hàng đợi và việc thực thi hàm tiếp tục ngay lập tức với bất kỳ mã nào sau lệnh resolve()gọi.

Chỉ khi vòng lặp sự kiện JS được cấp lại quyền kiểm soát thì lệnh gọi lại có thể bị xóa khỏi hàng đợi và thực sự được gọi.


1
Hàng đợi gọi lại được ghi lại trong A + Specs hay trong ES6?
thefourtheye

5
@thefourtheye: Đặc tả vòng lặp sự kiện thực sự là một phần của HTML5 . ES6 định nghĩa một phương thức nội bộ được gọi EnqueueJob, được gọi bởi .then.
Felix Kling

@thefourtheye: Trên thực tế, ES6 dường như cũng xác định hàng đợi: people.mozilla.org/~jorendorff/… . Tôi đoán liên quan đến vòng lặp sự kiện theo cách này hay cách khác.
Felix Kling

@FelixKling cảm ơn vì các liên kết - Tôi biết rằng đây là cách nó hoạt động, nhưng không thể trích dẫn chương và câu
Alnitak

2
@FelixKling đó là microtasks / macrotasks, đây là phần trong thông số kỹ thuật "phá vỡ" "Khi không có ngữ cảnh thực thi đang chạy và ngăn xếp ngữ cảnh thực thi trống, việc triển khai ECMAScript sẽ xóa PendingJob đầu tiên khỏi Hàng đợi công việc và sử dụng thông tin chứa trong đó để tạo bối cảnh thực thi và bắt đầu thực hiện thao tác tóm tắt Công việc được liên kết. "
Benjamin Gruenbaum
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.