Почему моя переменная не изменилась после того, как я изменил ее внутри функции? — асинхронная ссылка на код

Предисловия

Эта тема повторялась, по крайней мере, пару тысяч раз здесь, в Переполнении стека. Поэтому прежде всего я хотел бы указать на некоторые чрезвычайно полезные ресурсы:

Реализация собственной логики обратного вызова

Часто вам нужно делать больше вещей с результатом асинхронной функции или делать разные вещи с результатом в зависимости от того, где была вызвана асинхронная функция. Давайте рассмотрим более сложный пример:

 var outerScopeVar; helloCatAsync(); alert(outerScopeVar); function helloCatAsync() { setTimeout(function() { outerScopeVar = 'Nya'; }, Math.random() * 2000); } 

Примечание: я использую setTimeout со случайной задержкой в ​​качестве универсальной асинхронной функции, этот же пример применяется к Ajax, readFile , onload и любому другому асинхронному потоку.

Этот пример явно страдает от той же проблемы, что и другие примеры, он не ожидает выполнения асинхронной функции.

Давайте рассмотрим реализацию собственной системы обратного вызова. Во-первых, мы избавляемся от того уродливого outerScopeVar который в этом случае совершенно бесполезен. Затем мы добавляем параметр, который принимает аргумент функции, наш обратный вызов.

 // 1. Call helloCatAsync passing a callback function, // which will be called receiving the result from the async operation helloCatAsync(function(result) { // 5. Received the result from the async function, // now do whatever you want with it: alert(result); }); // 2. The "callback" parameter is a reference to the function which // was passed as argument from the helloCatAsync call function helloCatAsync(callback) { // 3. Start async operation: setTimeout(function() { // 4. Finished async operation, // call the callback passing the result as argument callback('Nya'); }, Math.random() * 2000); } 

Фрагмент кода из приведенного выше примера:

 // 1. Call helloCatAsync passing a callback function, // which will be called receiving the result from the async operation console.log("1. function called...") helloCatAsync(function(result) { // 5. Received the result from the async function, // now do whatever you want with it: console.log("5. result is: ", result); }); // 2. The "callback" parameter is a reference to the function which // was passed as argument from the helloCatAsync call function helloCatAsync(callback) { console.log("2. callback here is the function passed as argument above...") // 3. Start async operation: setTimeout(function() { console.log("3. start async operation...") console.log("4. finished async operation, calling the callback, passing the result...") // 4. Finished async operation, // call the callback passing the result as argument callback('Nya'); }, Math.random() * 2000); } 

Чаще всего в реальных случаях использования DOM API и большинство библиотек уже предоставляют функциональность обратного вызова (реализация helloCatAsync в этом демонстрационном примере). Вам нужно только передать функцию обратного вызова и понять, что она будет выполняться из синхронного потока, и реструктурировать свой код, чтобы приспособиться к этому.

Вы также заметите, что из-за асинхронного характера невозможно return значение из асинхронного потока обратно в синхронный поток, где был определен обратный вызов, поскольку асинхронные обратные вызовы выполняются задолго после того, как синхронный код уже завершен.

Вместо того, чтобы return значение из асинхронного обратного вызова, вам придется использовать шаблон обратного вызова или … Обещания.

обещания

Несмотря на то, что с помощью vanilla JS существуют способы, позволяющие не допускать обратного вызова, популярность обещаний растет и в настоящее время стандартизируется в ES6 (см. Обещание — MDN ).

Обещания (иначе Futures) обеспечивают более линейное и, следовательно, приятное чтение асинхронного кода, но объяснение всей их функциональности выходит за рамки этого вопроса. Вместо этого я оставлю эти отличные ресурсы для заинтересованных:

Ответ на поставленный вопрос

Давайте сначала проследим общее поведение. Во всех примерах outerScopeVar изменяется внутри функции . Эта функция явно не выполняется сразу, она присваивается или передается в качестве аргумента. Это то, что мы называем обратным вызовом .

Теперь вопрос в том, когда вызывается этот обратный вызов?

Это зависит от случая. Давайте попробуем снова проследить какое-то общее поведение:

  • img.onload может быть вызван когда-нибудь в будущем , когда (и если) изображение будет успешно загружено.
  • setTimeout может быть вызван когда-нибудь в будущем , после того, как задержка истечет, и время ожидания не будет отменено clearTimeout . Примечание: даже при использовании 0 качестве задержки все браузеры имеют минимальный предел задержки по времени (указанный в спецификации HTML5 равным 4 мс).
  • Обратный вызов jQuery $.post может быть вызван когда-нибудь в будущем , когда (и если) Ajax-запрос будет успешно выполнен.
  • fs.readFile Node.js может быть вызван когда-нибудь в будущем , когда файл будет успешно прочитан или возникла ошибка.

Во всех случаях у нас есть обратный вызов, который может быть запущен когда-нибудь в будущем . Это «когда-нибудь в будущем» мы называем асинхронным потоком .

Асинхронное выполнение выталкивается из синхронного потока. То есть асинхронный код никогда не будет выполняться во время выполнения стека синхронного кода. Это означает, что JavaScript является однопоточным.

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

Понравилась статья? Поделиться с друзьями:
JavaScript & TypeScript
Adblock
detector