Коли JavaScript синхронний?


202

Я мав враження, що JavaScript завжди був асинхронним. Однак я дізнався, що є ситуації, коли цього немає (тобто маніпуляції з DOM). Чи є десь хороша довідка про те, коли він буде синхронним і коли він буде асинхронним? Чи впливає jQuery на це взагалі?


14
Завжди за винятком аякса.
defau1t

Прийнята відповідь неправильна, а вводить в оману, люб’язно перевіряйте її.
Сурай Джайн

2
Також було корисно переглядати youtube.com/watch?v=8aGhZQkoFbQ, щоб зрозуміти цикл подій, а також як стек, веб-API та черга завдань працюють щодо синхронізації та асинхронізації
mtpultz

1
@ defau1t Це не так, JavaScript завжди синхронний, коли завершення дзвінка ajax закінчується зворотним викликом у черзі, як це виняток із синхронного характеру сценарію Java.
Сурай Джайн

Відповіді:


281

JavaScript завжди синхронний і однопотоковий. Якщо ви виконуєте код коду JavaScript на сторінці, то жоден інший JavaScript на цій сторінці наразі не виконується.

JavaScript є лише асинхронним у тому сенсі, який він може робити, наприклад, Ajax-дзвінки. Виклик Ajax припинить виконання, і інший код зможе виконуватись, поки виклик не повернеться (успішно чи інакше), і в цей момент зворотний виклик запуститься синхронно. На даний момент жоден інший код не працює. Він не перерве будь-який інший код, який зараз працює.

Таймери JavaScript працюють із цим самим видом зворотного дзвінка.

Опис JavaScript як асинхронного, можливо, вводить в оману. Точніше сказати, що JavaScript є синхронним та однопоточним з різними механізмами зворотного виклику.

jQuery має можливість викликів Ajax робити їх синхронно (з async: falseопцією). Початківці можуть спокуситись використовувати це неправильно, оскільки це дозволяє більш традиційну модель програмування, до якої можна було б звикнути. Причиною це проблематично є те, що ця опція заблокує весь JavaScript на сторінці до її завершення, включаючи всі обробники подій та таймери.


31
Вибачте, я не зовсім зрозумів це твердження "Код припинить виконуватись, поки виклик не повернеться (успішно чи помилково)". Ви могли б детальніше розробити. Як це твердження може бути правдивим, коли ви також говорите: "Він не перерве жодного іншого коду, що працює"; Ви говорите про код зворотних викликів лише в першій заяві? Будь ласка, просвіти мене.
кришна

2
Nettuts має підручник, який досить добре пояснює основи асинхронізації тут: net.tutsplus.com/tutorials/javascript-ajax/…
RobW

26
@cletus Заява "Код припиняє виконуватись, поки виклик не повернеться" потребує виправлення, оскільки виконання не зупиняється. Виконання коду може тривати. В іншому випадку це означатиме, що виклик синхронний.
ГС.

1
Я теж не зрозумів цього твердження.
буксирування

12
Ця відповідь неймовірно оманлива і заплутана. Будь ласка, дивіться натомість відповідь CMS або Faraz Ahmad.
іоно

214

JavaScript є однопоточним і має синхронну модель виконання. Одинарна різьба означає, що одночасно виконується одна команда. Синхронний означає один за одним, тобто виконується один рядок коду для того, щоб з'явився код. Так що в JavaScript одна справа відбувається одночасно.

Контекст виконання

Двигун JavaScript взаємодіє з іншими двигунами браузера. У стеці виконання JavaScript знаходиться глобальний контекст внизу, і тоді, коли ми викликаємо функції, механізм JavaScript створює нові контексти виконання для відповідних функцій. Коли викликана функція виходить, її контекст виконання вискакує з стека, а потім вискакується наступний контекст виконання і так далі ...

Наприклад

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


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

У наведеному вище коді буде створено глобальний контекст виконання, і в цьому контексті var oneбуде збережено і його значення буде 1 ... коли викликається виклик xyz (), тоді буде створено новий контекст виконання, і якщо ми визначили будь-яку змінну у функції xyz ці змінні зберігатимуться у контексті виконання xyz (). У функції xyz ми викликаємо abc (), а потім створюється контекст виконання abc () і ставиться у стек виконання ... Тепер, коли abc () закінчує, його контекст вискакує з стека, тоді контекст xyz () вискакує з стек і тоді глобальний контекст буде вискакуватися ...

Тепер про асинхронні зворотні дзвінки; асинхронний означає більше, ніж один.

Так само, як стек виконання, є черга подій . Коли ми хочемо отримувати сповіщення про якусь подію в механізмі JavaScript, ми можемо прослухати цю подію, і ця подія розміщується у черзі. Наприклад, подія запиту Ajax або подія HTTP-запиту.

Щоразу, коли стек виконання порожній, як показано у наведеному вище прикладі коду, JavaScript-механізм періодично переглядає чергу подій і бачить, чи існує якась подія, про яку потрібно повідомляти. Наприклад, у черзі було дві події: ajax-запит та HTTP-запит. Він також дивиться, чи є функція, яку потрібно запустити на цьому тригері події ... Отже, двигун JavaScript повідомляється про подію і знає відповідну функцію для виконання цієї події ... Отже, двигун JavaScript викликає функція обробника, у прикладі випадку, наприклад, AjaxHandler () буде викликана, і як завжди, коли функція викликається, її контекст виконання розміщується в контексті виконання, і тепер виконання функції завершується, а запит ajax події також видаляється з черги подій ... Коли AjaxHandler () закінчує стек виконання порожнім, двигун знову переглядає чергу подій і виконує функцію обробника подій HTTP-запиту, який був наступним у черзі. Важливо пам’ятати, що черга подій обробляється лише тоді, коли стек виконання порожній.

Наприклад, див. Код нижче, що пояснює виконання стека виконання та обробки черги подій за допомогою механізму 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>

Тепер запустіть веб-сторінку і натисніть на сторінку, і побачите вихід на консолі. Вихід буде

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

Двигун JavaScript виконує код синхронно, як пояснено в частині контексту виконання, браузер асинхронно ставить речі в чергу подій. Таким чином, функції, для виконання яких потрібно дуже багато часу, можуть перервати обробку подій. Речі, що відбуваються в браузері, як-от події, обробляються таким чином JavaScript, якщо є слухач, який повинен працювати, двигун запускатиме його, коли стек виконання порожній. І події обробляються в тому порядку, в якому вони відбуваються, тому асинхронна частина - це те, що відбувається поза двигуном, тобто що повинен робити двигун, коли трапляються ті зовнішні події.

Отже JavaScript завжди синхронний.


16
Ця відповідь дуже чітка, вона повинна отримати більше відгуків.
ranu

7
Звичайно, найкраще пояснення асинхронної поведінки Javascript, яку я прочитав.
Чарльз Хаймет

1
Приємне пояснення контексту виконання та черги виконання.
Дівяншу Майтані

1
Звичайно, це вимагає, щоб ви прочитали трохи про стек контексту виконання, і лише додавання його до порожнього та черга подій змушує мене нарешті відчути, що я розумію виправдання сценарію Java детерміновано. Що ще гірше, я відчуваю, що це займає лише сторінку читання, але я майже ніде не знаходжу її. То чому б ніхто просто не сказав цього? Або вони не знають, або що? Але я відчуваю, що якби підручник з js мав це, це могло б заощадити мені багато часу. >: |
маршальське ремесло

2
Ідеальне пояснення!
Джулсі

100

JavaScript є однопоточним, і ви весь час працюєте над нормальним виконанням синхронного кодового потоку.

Хорошими прикладами асинхронної поведінки, які може мати JavaScript, є події (взаємодія з користувачем, результати запиту Ajax тощо) та таймери, в основному дії, які можуть статися в будь-який час.

Я рекомендую вам поглянути на наступну статтю:

Ця стаття допоможе вам зрозуміти однопоточний характер JavaScript та як таймери працюють всередині та як працює асинхронне виконання JavaScript.

асинхронізація


Чи можна прийняти відповідь оману, чи можемо ми щось зробити в такому випадку? /
Сурай Джайн

8

Для когось, хто насправді розуміє, як працює JS, це питання може здатися незрозумілим, проте більшість людей, які використовують JS, не мають такого глибокого розуміння (і не обов'язково це потрібно), і для них це досить заплутаний момент, я буду спробуйте відповісти з цієї точки зору.

JS є синхронним у виконанні його коду. кожен рядок запускається лише після рядка до його завершення, і якщо цей рядок викликає функцію, після цього завершено ect ...

Основний момент плутанини виникає з того, що ваш браузер здатний сказати JS виправити більше коду в будь-який час (схоже на те, як ви можете виправити більше коду JS на сторінці з консолі). В якості прикладу JS має функції зворотного виклику, мета яких - дозволити АС синхронно ПОВЕДИТИсь, щоб інші частини JS могли працювати, очікуючи виконуваної функції JS (IE GETвиклик) для повернення відповіді, JS продовжуватиме працювати, поки браузер має відповідь, в цей момент цикл подій (браузер) виконає код JS, який викликає функцію зворотного дзвінка.

Оскільки цикл подій (браузер) може вводити більше JS для виконання у будь-якій точці, у цьому сенсі JS є асинхронним (основні речі, які спричинить браузер для введення JS-коду, - це тайм-аути, зворотні виклики та події)

Я сподіваюся, що це досить зрозуміло, щоб комусь було корисно.


4

Визначення

Термін "асинхронний" можна використовувати в дещо різних значеннях, в результаті чого тут, здавалося б, суперечливі відповіді, а насправді їх немає. У Вікіпедії про Асинхронію є таке визначення:

Асинхронність у комп'ютерному програмуванні стосується виникнення подій, незалежних від основного потоку програми та способів боротьби з такими подіями. Це можуть бути "зовнішні" події, такі як надходження сигналів або дії, викликані програмою, які відбуваються одночасно з виконанням програми, без блокування програми, щоб чекати результатів.

не-JavaScript код може чергувати такі "зовнішні" події до деяких черг подій JavaScript. Але це наскільки це йде.

Ніякого попередження

Немає зовнішнього переривання запуску коду JavaScript для виконання якогось іншого коду JavaScript у вашому сценарії. Частини JavaScript виконуються одна за одною, і порядок визначається порядком подій у кожній черзі подій та пріоритетом цих черг.

Наприклад, ви можете бути абсолютно впевнені, що жоден інший JavaScript (у тому ж сценарії) ніколи не буде виконуватися під час виконання наступного фрагмента коду:

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

Іншими словами, в JavaScript немає преференції . Що б не було в черзі подій, обробку цих подій доведеться почекати, поки такий фрагмент коду не закінчиться. Специфікація EcmaScript в розділі 8.4 Черги на роботу та завдання говорить :

Виконання завдання може бути ініційовано лише тоді, коли немає запущеного контексту виконання та стек контексту виконання порожній.

Приклади асинхронії

Як уже писали інші, є кілька ситуацій, коли асинхронія починає грати в JavaScript, і вона завжди включає чергу подій, яка може призвести до виконання JavaScript лише тоді, коли іншого коду JavaScript не виконується:

  • setTimeout(): агент (наприклад, браузер) поставить подію в чергу подій, коли закінчився час очікування. Моніторинг часу та розміщення події в черзі відбувається за допомогою коду, який не є JavaScript, і ви можете собі уявити, що це відбувається паралельно з можливим виконанням деякого коду JavaScript. Але зворотний виклик, що надається, setTimeoutможе виконуватись лише тоді, коли поточний виконуваний код JavaScript закінчився і читається відповідна черга подій.

  • fetch(): агент використовуватиме функції ОС для виконання запиту HTTP та моніторингу будь-якої вхідної відповіді. Знову ж таки, це завдання, яке не має JavaScript, може працювати паралельно з деяким кодом JavaScript, який все ще виконується. Але процедура вирішення обіцянки, яка вирішить повернуту обіцянку fetch(), може бути виконана лише тоді, коли поточний виконуваний JavaScript закінчився.

  • requestAnimationFrame(): двигун візуалізації браузера (не JavaScript) розміщує подію в черзі JavaScript, коли він буде готовий виконати операцію з фарбою. При обробці події JavaScript функція зворотного виклику виконується.

  • queueMicrotask(): негайно розміщує подію у черзі мікротеків. Зворотний виклик буде виконуватися, коли стек викликів порожній і ця подія використовується.

Є ще багато прикладів, але всі ці функції надаються хост-середовищем, а не ядром EcmaScript. З ядром EcmaScript ви можете синхронно розміщувати подію в черзі завдань обіцянки Promise.resolve().

Мовні конструкції

ECMAScript надає кілька мовних конструкцій для підтримки шаблону асинхронности, такі як yield, async, await. Але нехай не буде помилки: жоден JavaScript-код не буде перерваний зовнішньою подією. «Переривання» , що yieldі , awaitздається, забезпечують тільки контрольований, зумовлена спосіб повернення з виклику функції і відновлення його контексту виконання в подальшому, або JS код (у разі yield), або чергу подій (в разі await).

Обробка подій DOM

Коли код JavaScript отримує доступ до API DOM, це може в деяких випадках змусити API DOM викликати одне або більше синхронних сповіщень. І якщо у вашому коді є обробник подій, який слухає це, він буде викликаний.

Це може сприйматись як переважна паралельність, але це не так: як тільки ваш обробник подій повернеться, API DOM з часом також повернеться, і початковий код JavaScript продовжить.

В інших випадках API DOM буде просто відправити подію у відповідній черзі подій, а JavaScript вибере її після того, як стек викликів буде випорожнено.

Див. Синхронні та асинхронні події


0

Синхронний у всіх випадках.

Приклад блокування потоку за допомогою 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!');

Вихід буде:

Test start...
Test finish...
Finish!
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.