Когда JavaScript является синхронным?

202

У меня сложилось впечатление, что JavaScript всегда был асинхронным. Тем не менее, я узнал, что есть ситуации, когда это не так (например, манипуляции с DOM). Есть ли хорошая ссылка где-нибудь о том, когда он будет синхронным и когда он будет асинхронным? JQuery влияет на это вообще?

Брайан
источник
14
Всегда за исключением ajax.
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 или Фараз Ахмад.
Ионо
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
Этот ответ очень ясен, он должен получить больше голосов.
Рану
7
Конечно, лучшее объяснение асинхронного поведения Javascript, которое я читал.
Чарльз Хаймет
1
Хорошее объяснение контекста исполнения и очереди.
Divyanshu Maithani
1
Конечно, это требует, чтобы вы прочитали немного о стеке контекста выполнения, и только добавление его пустым и события que заставляет меня наконец почувствовать, что я понимаю исполнение java-сценария детерминистически. Что еще хуже, я чувствую, что это занимает только страницу чтения, но я нахожу это почти нигде. Так почему никто просто не скажет это? Или они не знают или что? Но я чувствую, что если бы у js-руководства было это, оно могло бы сэкономить мне много времени. >: |
маршал крафт
2
Идеальное объяснение!
Julsy
100

JavaScript является однопоточным, и все время вы работаете над нормальным синхронным выполнением потока кода.

Хорошими примерами асинхронного поведения, которое может иметь JavaScript, являются события (взаимодействие с пользователем, результаты запроса Ajax и т. Д.) И таймеры, в основном действия, которые могут произойти в любое время.

Я бы порекомендовал вам взглянуть на следующую статью:

Эта статья поможет вам понять однопотоковую природу JavaScript и то, как таймеры работают внутри и как работает асинхронное выполнение JavaScript.

асинхронной

CMS
источник
Принятый ответ вводит в заблуждение, можем ли мы что-то сделать в этом случае? /
Сурадж Джейн
8

Кому-то, кто действительно понимает, как работает JS, этот вопрос может показаться странным, однако большинство людей, использующих JS, не имеют такого глубокого понимания (и не обязательно это нужно), и для них это довольно запутанный вопрос, я буду попытаться ответить с этой точки зрения.

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

Основная путаница возникает из-за того, что ваш браузер может сказать JS извинять больше кода в любое время (аналогично тому, как вы можете извинить больше кода 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 с помощьюPromise.resolve() .

Языковые конструкции

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

Обработка событий DOM

Когда код JavaScript обращается к DOM API, это может в некоторых случаях заставить DOM API вызывать одно или несколько синхронных уведомлений. И если в вашем коде есть обработчик событий, который его прослушивает, он будет вызван.

Это может показаться упреждающим параллелизмом, но это не так: как только ваш обработчик (и) событий вернет (а), DOM API в конечном итоге также вернется, и оригинальный код JavaScript продолжится.

В других случаях DOM API просто отправит событие в соответствующую очередь событий, а JavaScript заберет его после очистки стека вызовов.

Смотрите синхронные и асинхронные события

trincot
источник
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!
Эдуардо Куомо
источник