Khi nào JavaScript đồng bộ?


202

Tôi đã có ấn tượng rằng JavaScript luôn không đồng bộ. Tuy nhiên, tôi đã học được rằng có những tình huống không phải như vậy (tức là thao tác DOM). Có một tài liệu tham khảo tốt ở bất cứ đâu về khi nào nó sẽ được đồng bộ và khi nào nó sẽ không đồng bộ? JQuery có ảnh hưởng gì đến điều này không?


14
Luôn luôn ngoại trừ ajax.
defau1t

Câu trả lời được chấp nhận là sai, và sai lệch, vui lòng kiểm tra nó.
Suraj Jain

2
Cũng rất hữu ích khi xem youtube.com/watch?v=8aGhZQkoFbQ để hiểu vòng lặp sự kiện và cách ngăn xếp, API web và hàng đợi tác vụ liên quan đến đồng bộ hóa và async
mtpultz

1
@ defau1t Điều này không sai, JavaScript luôn đồng bộ, khi cuộc gọi ajax kết thúc cuộc gọi lại kết thúc trong hàng đợi, làm thế nào là một ngoại lệ đối với bản chất đồng bộ của tập lệnh java.
Suraj Jain

Câu trả lời:


281

JavaScript luôn đồng bộ và đơn luồng. Nếu bạn đang thực thi một khối mã JavaScript trên một trang thì hiện tại sẽ không có JavaScript nào khác trên trang đó được thực thi.

JavaScript chỉ không đồng bộ theo nghĩa mà nó có thể thực hiện, ví dụ, các lệnh gọi Ajax. Cuộc gọi Ajax sẽ dừng thực thi và mã khác sẽ có thể thực hiện cho đến khi cuộc gọi trở lại (thành công hoặc theo cách khác), tại thời điểm đó, cuộc gọi lại sẽ chạy đồng bộ. Không có mã khác sẽ được chạy tại thời điểm này. Nó sẽ không làm gián đoạn bất kỳ mã nào khác hiện đang chạy.

Bộ định thời JavaScript hoạt động với cùng loại gọi lại này.

Mô tả JavaScript là không đồng bộ có lẽ là sai lệch. Chính xác hơn để nói rằng JavaScript là đồng bộ và đơn luồng với các cơ chế gọi lại khác nhau.

jQuery có một tùy chọn trên các lệnh gọi Ajax để thực hiện chúng một cách đồng bộ (với async: falsetùy chọn). Người mới bắt đầu có thể bị cám dỗ để sử dụng không chính xác bởi vì nó cho phép một mô hình lập trình truyền thống hơn mà người ta có thể sử dụng nhiều hơn. Lý do nó có vấn đề là tùy chọn này sẽ chặn tất cả JavaScript trên trang cho đến khi hoàn thành, bao gồm tất cả các trình xử lý sự kiện và bộ hẹn giờ.


31
xin lỗi, tôi hoàn toàn không hiểu câu lệnh này "Mã sẽ dừng thực thi cho đến khi cuộc gọi trở lại (thành công hoặc bị lỗi)". bạn có thể giải thích Làm thế nào câu nói đó có thể đúng khi bạn cũng nói "Nó sẽ không làm gián đoạn bất kỳ mã nào khác đang chạy"; Bạn đang nói về mã gọi lại chỉ trong câu lệnh đầu tiên? Vui lòng làm sáng tỏ cho tôi.
krishna

2
Nettuts có một hướng dẫn đó là khá tốt tại giải thích những điều cơ bản của async ở đây: net.tutsplus.com/tutorials/javascript-ajax/...
RobW

26
@cletus Câu lệnh "Mã sẽ dừng thực thi cho đến khi cuộc gọi trở lại" cần sửa vì thực thi không dừng. Việc thực thi mã có thể tiếp tục. Nếu không, nó có nghĩa là cuộc gọi là đồng bộ.
HS.

1
Tôi cũng không hiểu câu nói đó.
kéo xe

12
Câu trả lời này là vô cùng sai lệch và khó hiểu. Thay vào đó, vui lòng xem câu trả lời của CMS hoặc Faraz Ahmad.
iono

214

JavaScript là một luồng đơn và có một mô hình thực thi đồng bộ. Luồng đơn có nghĩa là một lệnh đang được thực thi tại một thời điểm. Đồng bộ nghĩa là mỗi lần một mã tức là một dòng mã đang được thực thi theo thứ tự mã xuất hiện. Vì vậy, trong JavaScript một điều đang xảy ra tại một thời điểm.

Bối cảnh thực hiện

Công cụ JavaScript tương tác với các công cụ khác trong trình duyệt. Trong ngăn xếp thực thi JavaScript có bối cảnh toàn cầu ở phía dưới và sau đó khi chúng ta gọi các hàm, công cụ JavaScript tạo bối cảnh thực thi mới cho các hàm tương ứng. Khi hàm được gọi thoát khỏi bối cảnh thực thi của nó được bật ra khỏi ngăn xếp, và sau đó bối cảnh thực thi tiếp theo được bật lên và cứ thế ...

Ví dụ

function abc()
{
   console.log('abc');
}


function xyz()
{
   abc()
   console.log('xyz');
}
var one = 1;
xyz();

Trong đoạn mã trên, bối cảnh thực thi toàn cầu sẽ được tạo và trong bối cảnh này var one này sẽ được lưu trữ và giá trị của nó sẽ là 1 ... khi lệnh gọi xyz () được gọi thì bối cảnh thực thi mới sẽ được tạo và nếu chúng ta đã xác định bất kỳ biến nào trong hàm xyz, các biến đó sẽ được lưu trữ trong ngữ cảnh thực thi của xyz (). Trong hàm xyz, chúng ta gọi abc () và sau đó bối cảnh thực thi abc () được tạo và đưa vào ngăn xếp thực thi ... Bây giờ khi abc () kết thúc bối cảnh của nó được bật lên từ ngăn xếp, thì bối cảnh xyz () được bật lên từ ngăn xếp và sau đó bối cảnh toàn cầu sẽ xuất hiện ...

Bây giờ về các cuộc gọi lại không đồng bộ; không đồng bộ có nghĩa là nhiều hơn một lần.

Giống như ngăn xếp thực thi, có Hàng đợi sự kiện . Khi chúng tôi muốn được thông báo về một số sự kiện trong công cụ JavaScript, chúng tôi có thể lắng nghe sự kiện đó và sự kiện đó được đặt trên hàng đợi. Ví dụ: một sự kiện yêu cầu Ajax hoặc sự kiện yêu cầu HTTP.

Bất cứ khi nào ngăn xếp thực thi trống, như được hiển thị trong ví dụ mã ở trên, công cụ JavaScript định kỳ xem hàng đợi sự kiện và xem liệu có bất kỳ sự kiện nào được thông báo hay không. Ví dụ: trong hàng đợi có hai sự kiện, yêu cầu ajax và yêu cầu HTTP. Nó cũng xem liệu có một chức năng nào cần được chạy trên trình kích hoạt sự kiện đó không ... Vì vậy, công cụ JavaScript được thông báo về sự kiện đó và biết chức năng tương ứng để thực thi trong sự kiện đó ... Vì vậy, công cụ JavaScript gọi Hàm xử lý, trong trường hợp ví dụ, vd ... Khi AjaxHandler () hoàn thành ngăn xếp thực thi trống để động cơ lại nhìn vào hàng đợi sự kiện và chạy chức năng xử lý sự kiện của yêu cầu HTTP tiếp theo trong hàng đợi. Điều quan trọng cần nhớ là hàng đợi sự kiện chỉ được xử lý khi ngăn xếp thực thi trống.

Ví dụ, xem mã bên dưới giải thích ngăn xếp thực thi và xử lý hàng đợi sự kiện bằng công cụ Javascript.

function waitfunction() {
    var a = 5000 + new Date().getTime();
    while (new Date() < a){}
    console.log('waitfunction() context will be popped after this line');
}

function clickHandler() {
    console.log('click event handler...');   
}

document.addEventListener('click', clickHandler);


waitfunction(); //a new context for this function is created and placed on the execution stack
console.log('global context will be popped after this line');

<html>
    <head>

    </head>
    <body>

        <script src="program.js"></script>
    </body>
</html>

Bây giờ hãy chạy trang web và nhấp vào trang và xem đầu ra trên bàn điều khiển. Đầu ra sẽ là

waitfunction() context will be popped after this line
global context will be emptied after this line
click event handler...

Công cụ JavaScript đang chạy mã đồng bộ như được giải thích trong phần bối cảnh thực thi, trình duyệt đang đặt không đồng bộ mọi thứ vào hàng đợi sự kiện. Vì vậy, các chức năng mất nhiều thời gian để hoàn thành có thể làm gián đoạn xử lý sự kiện. Những điều xảy ra trong trình duyệt như các sự kiện được xử lý theo cách này bằng JavaScript, nếu có một trình nghe được cho là chạy, công cụ sẽ chạy nó khi ngăn xếp thực thi trống. Và các sự kiện được xử lý theo thứ tự chúng xảy ra, vì vậy phần không đồng bộ là về những gì đang xảy ra bên ngoài động cơ, tức là động cơ nên làm gì khi những sự kiện bên ngoài đó xảy ra.

Vì vậy, JavaScript luôn đồng bộ.


16
Câu trả lời này rất rõ ràng, nó sẽ nhận được nhiều upvote hơn.
ranu

7
Chắc chắn là lời giải thích tốt nhất cho hành vi không đồng bộ của Javascript mà tôi đã đọc.
Charles Jaimet

1
Giải thích tốt về bối cảnh thực hiện và xếp hàng.
Divyanshu Maithani

1
Tất nhiên, điều này đòi hỏi bạn phải đọc một chút về ngăn xếp bối cảnh thực thi và chỉ việc thêm nó vào chỗ trống và hàng đợi sự kiện làm cho tôi cuối cùng cảm thấy như tôi hiểu rõ về việc loại bỏ đoạn mã java. Điều tồi tệ hơn là tôi cảm thấy nó chỉ mất một trang đọc nhưng tôi thấy nó hầu như không ở đâu cả. Vậy tại sao không ai chỉ nói vậy? Họ không biết hay sao? Nhưng tôi cảm thấy nếu một hướng dẫn js có nó, nó có thể giúp tôi tiết kiệm rất nhiều thời gian. >: |
soái ca thủ công

2
Giải thích hoàn hảo!
Julsy

100

JavaScript là một luồng đơn và tất cả thời gian bạn làm việc trên một thực thi dòng mã đồng bộ thông thường.

Các ví dụ hay về hành vi không đồng bộ mà JavaScript có thể có là các sự kiện (tương tác người dùng, kết quả yêu cầu Ajax, v.v.) và bộ định thời, về cơ bản là các hành động có thể xảy ra bất cứ lúc nào.

Tôi muốn giới thiệu bạn để xem bài viết sau:

Bài viết đó sẽ giúp bạn hiểu bản chất đơn luồng của JavaScript và cách bộ định thời hoạt động bên trong và cách thực thi JavaScript không đồng bộ.

không đồng bộ


Câu trả lời sai được chấp nhận chúng ta có thể làm gì trong trường hợp đó không? /
Suraj Jain

8

Đối với một người thực sự hiểu cách JS hoạt động, câu hỏi này có vẻ tắt, tuy nhiên hầu hết những người sử dụng JS không có mức độ hiểu biết sâu sắc như vậy (và không nhất thiết cần nó) và với họ đây là một điểm khá khó hiểu, tôi sẽ cố gắng trả lời từ quan điểm đó.

JS là đồng bộ theo cách mã của nó được thực thi. mỗi dòng chỉ chạy sau dòng trước khi nó hoàn thành và nếu dòng đó gọi một hàm sau khi hoàn thành ...

Điểm chính của sự nhầm lẫn xuất phát từ thực tế là trình duyệt của bạn có thể yêu cầu JS xử lý nhiều mã hơn bất cứ lúc nào (simmlar để làm thế nào bạn có thể loại bỏ nhiều mã JS hơn trên một trang từ bảng điều khiển). Như một ví dụ về JS có các hàm Callback, mục đích của nó là cho phép JS BEHAVE không đồng bộ để các phần tiếp theo của JS có thể chạy trong khi chờ đợi một hàm JS đã được thực thi (IE một GETcuộc gọi) để trả về câu trả lời, JS sẽ tiếp tục chạy cho đến khi trình duyệt có câu trả lời tại thời điểm đó, vòng lặp sự kiện (trình duyệt) sẽ thực thi mã JS gọi hàm gọi lại.

Vì vòng lặp sự kiện (trình duyệt) có thể nhập thêm JS để được thực thi tại bất kỳ thời điểm nào theo nghĩa đó, nên JS không đồng bộ (những điều chính sẽ khiến trình duyệt nhập mã JS là thời gian chờ, cuộc gọi lại và sự kiện)

Tôi hy vọng điều này là đủ rõ ràng để hữu ích cho ai đó.


4

Định nghĩa

Thuật ngữ "không đồng bộ" có thể được sử dụng theo các nghĩa hơi khác nhau, dẫn đến các câu trả lời dường như mâu thuẫn ở đây, trong khi chúng thực sự không. Wikipedia về không đồng bộ có định nghĩa này:

Không đồng bộ, trong lập trình máy tính, đề cập đến sự xuất hiện của các sự kiện độc lập với luồng chương trình chính và các cách để đối phó với các sự kiện đó. Đây có thể là các sự kiện "bên ngoài" như sự xuất hiện của tín hiệu hoặc hành động do chương trình khởi xướng đồng thời với việc thực hiện chương trình mà không có chương trình chặn để chờ kết quả.

mã không phải JavaScript có thể xếp hàng các sự kiện "bên ngoài" như vậy vào một số hàng đợi sự kiện của JavaScript. Nhưng đó là xa như nó đi.

Không có quyền ưu tiên

Không có sự gián đoạn bên ngoài của việc chạy mã JavaScript để thực thi một số mã JavaScript khác trong tập lệnh của bạn. Các mảnh JavaScript được thực hiện lần lượt từng thứ tự và thứ tự được xác định theo thứ tự các sự kiện trong mỗi hàng đợi sự kiện và mức độ ưu tiên của các hàng đợi đó.

Chẳng hạn, bạn có thể hoàn toàn chắc chắn rằng sẽ không có JavaScript nào khác (trong cùng một tập lệnh) thực thi trong khi đoạn mã sau đang thực thi:

let a = [1, 4, 15, 7, 2];
let sum = 0;
for (let i = 0; i < a.length; i++) {
    sum += a[i];
}

Nói cách khác, không có quyền ưu tiên trong JavaScript. Bất cứ điều gì có thể có trong hàng đợi sự kiện, việc xử lý các sự kiện đó sẽ phải đợi cho đến khi đoạn mã đó chạy đến khi hoàn thành. Đặc tả EcmaScript cho biết trong phần 8.4 Công việc và Hàng đợi Công việc :

Việc thực thi một công việc chỉ có thể được bắt đầu khi không có bối cảnh thực thi đang chạy và ngăn xếp bối cảnh thực thi trống.

Ví dụ về sự không đồng bộ

Như những người khác đã viết, có một số tình huống trong đó tính không đồng bộ xuất hiện trong JavaScript và nó luôn liên quan đến hàng đợi sự kiện, điều này chỉ có thể dẫn đến việc thực thi JavaScript khi không có mã JavaScript khác thực thi:

  • setTimeout(): tác nhân (ví dụ: trình duyệt) sẽ đặt một sự kiện vào hàng đợi sự kiện khi hết thời gian chờ. Việc theo dõi thời gian và việc đặt sự kiện trong hàng đợi xảy ra bởi mã không phải là JavaScript và do đó bạn có thể tưởng tượng điều này xảy ra song song với việc thực thi tiềm năng của một số mã JavaScript. Nhưng cuộc gọi lại được cung cấp setTimeoutchỉ có thể thực thi khi mã JavaScript hiện đang chạy đã hoàn tất và hàng đợi sự kiện thích hợp đang được đọc.

  • fetch(): tác nhân sẽ sử dụng các chức năng của HĐH để thực hiện yêu cầu HTTP và theo dõi mọi phản hồi đến. Một lần nữa, tác vụ không phải JavaScript này có thể chạy song song với một số mã JavaScript vẫn đang thực thi. Nhưng thủ tục giải quyết lời hứa, sẽ giải quyết lời hứa được trả về fetch(), chỉ có thể thực thi khi JavaScript hiện đang thực thi đã hoàn tất.

  • requestAnimationFrame(): công cụ kết xuất của trình duyệt (không phải JavaScript) sẽ đặt một sự kiện vào hàng đợi JavaScript khi nó sẵn sàng thực hiện thao tác vẽ. Khi sự kiện JavaScript được xử lý, chức năng gọi lại được thực thi.

  • queueMicrotask(): ngay lập tức đặt một sự kiện trong hàng đợi microtask. Cuộc gọi lại sẽ được thực hiện khi ngăn xếp cuộc gọi trống và sự kiện đó được tiêu thụ.

Có nhiều ví dụ khác, nhưng tất cả các chức năng này được cung cấp bởi môi trường máy chủ, không phải bởi lõi EcmaScript. Với EcmaScript lõi, bạn có thể đặt đồng bộ một sự kiện vào Hàng đợi công việc đầy hứa hẹn với Promise.resolve().

Ngôn ngữ xây dựng

ECMAScript cung cấp một số cấu trúc ngôn ngữ để hỗ trợ các mô hình không đồng pha, chẳng hạn như yield, async, await. Nhưng đừng để nhầm lẫn: không có mã JavaScript nào bị gián đoạn bởi một sự kiện bên ngoài. Các "gián đoạn" đó yieldawaitdường như cung cấp chỉ là một kiểm soát, cách xác định trước trở về từ một cuộc gọi chức năng và khôi phục bối cảnh thực hiện của nó sau này, hoặc bằng cách mã JS (trong trường hợp yield), hoặc hàng đợi sự kiện (trong trường hợp await).

Xử lý sự kiện DOM

Khi mã JavaScript truy cập API DOM, trong một số trường hợp, điều này có thể khiến API DOM kích hoạt một hoặc nhiều thông báo đồng bộ. Và nếu mã của bạn có một trình xử lý sự kiện lắng nghe điều đó, nó sẽ được gọi.

Điều này có thể xuất hiện dưới dạng đồng thời có trước, nhưng không phải là: một khi (các) trình xử lý sự kiện của bạn trả về, API DOM cuối cùng cũng sẽ trả về và mã JavaScript gốc sẽ tiếp tục.

Trong các trường hợp khác, API DOM sẽ chỉ gửi một sự kiện trong hàng đợi sự kiện thích hợp và JavaScript sẽ nhận nó sau khi ngăn xếp cuộc gọi đã được xóa.

Xem các sự kiện đồng bộ và không đồng bộ


0

Là đồng bộ trên tất cả các trường hợp.

Ví dụ về chặn luồng với Promises:

  const test = () => new Promise((result, reject) => {
    const time = new Date().getTime() + (3 * 1000);

    console.info('Test start...');

    while (new Date().getTime() < time) {
      // Waiting...
    }

    console.info('Test finish...');
  });

  test()
    .then(() => console.info('Then'))
    .finally(() => console.info('Finally'));

  console.info('Finish!');

Đầu ra sẽ là:

Test start...
Test finish...
Finish!
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.