Учитывая следующие примеры, почему не outerScopeVar
определено во всех случаях?
var outerScopeVar;
var img = document.createElement('img');
img.onload = function() {
outerScopeVar = this.width;
};
img.src = 'lolcat.png';
alert(outerScopeVar);
var outerScopeVar;
setTimeout(function() {
outerScopeVar = 'Hello Asynchronous World!';
}, 0);
alert(outerScopeVar);
// Example using some jQuery
var outerScopeVar;
$.post('loldog', function(response) {
outerScopeVar = response;
});
alert(outerScopeVar);
// Node.js example
var outerScopeVar;
fs.readFile('./catdog.html', function(err, data) {
outerScopeVar = data;
});
console.log(outerScopeVar);
// with promises
var outerScopeVar;
myPromise.then(function (response) {
outerScopeVar = response;
});
console.log(outerScopeVar);
// geolocation API
var outerScopeVar;
navigator.geolocation.getCurrentPosition(function (pos) {
outerScopeVar = pos;
});
console.log(outerScopeVar);
Почему он выводит undefined
во всех этих примерах? Я не хочу обходных путей, я хочу знать, почему это происходит.
Примечание. Это канонический вопрос для асинхронности JavaScript . Не стесняйтесь улучшать этот вопрос и добавлять более упрощенные примеры, с которыми сообщество может идентифицировать.
javascript
asynchronous
Фабрицио Матте
источник
источник
Ответы:
Одним словом ответ: асинхронность .
Предисловия
Эта тема повторялась, по крайней мере, пару тысяч раз здесь, в Переполнении стека. Поэтому прежде всего я хотел бы указать на некоторые чрезвычайно полезные ресурсы:
@ Ответ Феликса Клинга на «Как вернуть ответ от асинхронного вызова» . Смотрите его превосходный ответ, объясняющий синхронные и асинхронные потоки, а также раздел «Реструктуризация кода».
@ Бенджамин Грюнбаум также приложил много усилий для объяснения асинхронности в той же теме.
Ответ @Matt Esch «Получить данные из fs.readFile» также очень просто объясняет асинхронность.
Ответ на поставленный вопрос
Давайте сначала проследим общее поведение. Во всех примерах
outerScopeVar
модификация внутри функции . Эта функция явно не выполняется сразу, она присваивается или передается в качестве аргумента. Это то, что мы называем обратным вызовом .Теперь вопрос в том, когда вызывается этот обратный вызов?
Это зависит от случая. Давайте попробуем снова проследить какое-то общее поведение:
img.onload
может быть вызвано когда-нибудь в будущем , когда (и если) изображение будет успешно загружено.setTimeout
может быть вызван когда-нибудь в будущем , после того, как задержка истекла, и тайм-аут не был отмененclearTimeout
. Примечание: даже при использовании в0
качестве задержки все браузеры имеют минимальный предел времени ожидания (указанный в спецификации HTML5 равным 4 мс).$.post
Обратный вызов jQuery может быть вызван когда-нибудь в будущем , когда (и если) запрос Ajax будет успешно выполнен.fs.readFile
могут быть вызваны когда-нибудь в будущем , когда файл будет успешно прочитан или возникла ошибка.Во всех случаях у нас есть обратный вызов, который может быть запущен когда-нибудь в будущем . Это «когда-нибудь в будущем» мы называем асинхронным потоком .
Асинхронное выполнение выталкивается из синхронного потока. То есть асинхронный код никогда не будет выполняться во время выполнения стека синхронного кода. Это означает, что JavaScript является однопоточным.
Более конкретно, когда механизм JS находится в режиме ожидания, не выполняя стек (a) синхронного кода, он будет запрашивать события, которые могли вызвать асинхронные обратные вызовы (например, истекло время ожидания, полученный сетевой ответ), и выполнять их один за другим. Это рассматривается как Event Loop .
То есть асинхронный код, выделенный в нарисованных от руки красных формах, может выполняться только после того, как весь оставшийся синхронный код в их соответствующих кодовых блоках выполнен:
Короче говоря, функции обратного вызова создаются синхронно, но выполняются асинхронно. Вы просто не можете полагаться на выполнение асинхронной функции, пока не узнаете, что она выполнена, и как это сделать?
Это действительно просто. Логика, которая зависит от выполнения асинхронной функции, должна запускаться / вызываться из этой асинхронной функции. Например, перемещение
alert
s иconsole.log
s внутри функции обратного вызова выдаст ожидаемый результат, потому что результат доступен в этой точке.Реализация собственной логики обратного вызова
Часто вам нужно делать больше вещей с результатом асинхронной функции или делать разные вещи с результатом в зависимости от того, где была вызвана асинхронная функция. Давайте рассмотрим более сложный пример:
Примечание: Я использую
setTimeout
со случайной задержкой в качестве универсального асинхронного функции, тот же самый пример относится к Ajax,readFile
,onload
а также любые другие асинхронные потока.Этот пример явно страдает от той же проблемы, что и другие примеры, он не ожидает выполнения асинхронной функции.
Давайте рассмотрим реализацию собственной системы обратного вызова. Во-первых, мы избавляемся от того уродства,
outerScopeVar
которое в этом случае совершенно бесполезно. Затем мы добавляем параметр, который принимает аргумент функции, наш обратный вызов. Когда асинхронная операция заканчивается, мы вызываем этот обратный вызов, передавая результат. Реализация (пожалуйста, прочитайте комментарии по порядку):Фрагмент кода из приведенного выше примера:
Чаще всего в реальных случаях использования DOM API и большинство библиотек уже предоставляют функциональность обратного вызова (
helloCatAsync
реализация в этом демонстрационном примере). Вам нужно только передать функцию обратного вызова и понять, что она будет выполняться из синхронного потока, и реструктурировать свой код, чтобы приспособиться к этому.Вы также заметите, что из-за асинхронного характера невозможно передать
return
значение из асинхронного потока обратно в синхронный поток, в котором был определен обратный вызов, поскольку асинхронные обратные вызовы выполняются задолго после того, как синхронный код уже завершен.Вместо того, чтобы
return
извлекать значение из асинхронного обратного вызова, вам придется использовать шаблон обратного вызова или ... Обещания.обещания
Несмотря на то, что с помощью vanilla JS существуют способы, позволяющие не допускать обратного вызова, популярность обещаний растет и в настоящее время стандартизируется в ES6 (см. Обещание - MDN ).
Обещания (иначе Futures) обеспечивают более линейное и, следовательно, приятное чтение асинхронного кода, но объяснение всей их функциональности выходит за рамки этого вопроса. Вместо этого я оставлю эти отличные ресурсы для заинтересованных:
Больше материалов для чтения об асинхронности JavaScript
источник
Ответ Фабрисио точен; но я хотел дополнить его ответ чем-то менее техническим, сосредоточенным на аналогии, чтобы помочь объяснить концепцию асинхронности .
Аналогия ...
Вчера работа, которую я выполнял, требовала информации от коллеги. Я позвонил ему; вот как прошел разговор:
В этот момент я повесил трубку. Поскольку мне нужна была информация от Боба, чтобы заполнить свой отчет, я оставил отчет и вместо этого пошел выпить кофе, а потом получил какое-то электронное письмо. 40 минут спустя (Боб медленно), Боб перезвонил и дал мне информацию, в которой я нуждался. На этом этапе я возобновил свою работу со своим отчетом, так как у меня была вся необходимая информация.
Представьте, если бы разговор пошел так, как это;
И я сидел там и ждал. И ждал. И ждал. На 40 минут. Ничего не делая, кроме ожидания. В конце концов, Боб дал мне информацию, мы повесили трубку, и я закончил свой отчет. Но я потерял 40 минут производительности.
Это асинхронное и синхронное поведение
Это именно то, что происходит во всех примерах в нашем вопросе. Загрузка изображения, загрузка файла с диска и запрос страницы через AJAX - все это медленные операции (в контексте современных вычислений).
Вместо ожидания завершения этих медленных операций JavaScript позволяет зарегистрировать функцию обратного вызова, которая будет выполнена после завершения медленной операции. Тем временем, однако, JavaScript продолжит выполнять другой код. Тот факт, что JavaScript выполняет другой код , ожидая завершения медленной операции, делает поведение асинхронным . Если бы JavaScript ожидал завершения операции перед выполнением любого другого кода, это было бы синхронным поведением.
В приведенном выше коде мы просим загрузить JavaScript
lolcat.png
, что является сложным процессом . Функция обратного вызова будет выполнена, как только эта медленная операция будет выполнена, но тем временем JavaScript продолжит обрабатывать следующие строки кода; то естьalert(outerScopeVar)
.Вот почему мы видим предупреждение
undefined
; так какalert()
обрабатывается сразу, а не после того, как изображение было загружено.Чтобы исправить наш код, все, что нам нужно сделать, это переместить
alert(outerScopeVar)
код в функцию обратного вызова. Как следствие этого нам больше не нужнаouterScopeVar
переменная, объявленная как глобальная переменная.Вы всегда увидите, что обратный вызов указан как функция, потому что это единственный * способ в JavaScript определить некоторый код, но не выполнять его до тех пор, пока он не будет выполнен позже.
Следовательно, во всех наших примерах
function() { /* Do something */ }
это обратный вызов; чтобы исправить все примеры, все, что нам нужно сделать, это переместить туда код, который нуждается в ответе операции!* Технически вы также можете использовать
eval()
, ноeval()
это зло для этой целиКак мне заставить своего звонящего ждать?
В настоящее время вы можете иметь некоторый код, похожий на этот;
Однако теперь мы знаем, что это
return outerScopeVar
происходит немедленно; до того, какonload
функция обратного вызова обновила переменную. Это приводит кgetWidthOfImage()
возвращениюundefined
иundefined
предупреждению.Чтобы это исправить, нам нужно разрешить вызывающей функции
getWidthOfImage()
зарегистрировать обратный вызов, а затем переместить оповещение ширины в этот обратный вызов;... как и прежде, обратите внимание, что мы смогли удалить глобальные переменные (в данном случае
width
).источник
Вот более краткий ответ для людей, которые ищут краткий справочник, а также некоторые примеры, использующие обещания и async / await.
Начните с наивного подхода (который не работает) для функции, которая вызывает асинхронный метод (в данном случае
setTimeout
) и возвращает сообщение:undefined
регистрируется в этом случае, потому чтоgetMessage
возвращается доsetTimeout
вызова и обновляетсяouterScopeVar
.Два основных способа решить эту проблему - использовать обратные вызовы и обещания :
Callbacks
Здесь изменение заключается в том, что он
getMessage
принимаетcallback
параметр, который будет вызываться для доставки результатов обратно в вызывающий код, когда он станет доступен.обещания
Обещания предоставляют альтернативу, которая является более гибкой, чем обратные вызовы, поскольку их можно естественным образом объединить для координации нескольких асинхронных операций. Promises / A + стандартная реализация изначально предусмотрен в Node.js (0.12+) и многих современных браузерах, но также реализован в библиотеках , как Bluebird и Q .
jQuery Deferreds
JQuery предоставляет функциональность, аналогичную обещаниям с его отложенными.
асинхронная / Await
Если в вашей среде JavaScript есть поддержка
async
иawait
(например, Node.js 7.6+), вы можете синхронно использовать обещания внутриasync
функций:источник
function getMessage(param1, param2, callback) {...}
.async/await
образец, но у меня проблемы. Вместо того, чтобы создавать экземплярnew Promise
, я делаю.Get()
вызов и поэтому не имею доступа ни к какомуresolve()
методу. Таким образом, мойgetMessage()
возвращает обещание, а не результат. Не могли бы вы немного отредактировать свой ответ, чтобы показать рабочий синтаксис для этого?.Get()
звонишь. Наверное, лучше всего написать новый вопрос.Чтобы заявить очевидное, чашка представляет
outerScopeVar
.Асинхронные функции будут как ...
источник
Другие ответы превосходны, и я просто хочу дать прямой ответ на этот вопрос. Просто ограничение jQuery асинхронными вызовами
Все вызовы ajax (включая
$.get
или$.post
или$.ajax
) являются асинхронными.Учитывая ваш пример
Выполнение кода начинается со строки 1, объявляет переменную и триггеры, а также асинхронный вызов в строке 2 (т. Е. После запроса) и продолжает выполнение со строки 3, не дожидаясь завершения запроса после завершения запроса.
Допустим, что почтовый запрос завершается за 10 секунд, значение
outerScopeVar
будет установлено только после этих 10 секунд.Попробовать,
Теперь, когда вы выполните это, вы получите предупреждение в строке 3. Теперь подождите некоторое время, пока вы не убедитесь, что почтовый запрос вернул какое-то значение. Затем, когда вы нажмете OK, в окне оповещений следующее оповещение выведет ожидаемое значение, потому что вы его ждали.
В реальной жизни код становится
Весь код, который зависит от асинхронных вызовов, перемещается внутри асинхронного блока или путем ожидания асинхронных вызовов.
источник
or by waiting on the asynchronous calls
Как это сделать?Во всех этих сценариях
outerScopeVar
изменяется или присваивается значение асинхронно или происходит в более позднее время (ожидание или прослушивание какого-либо события), для которого текущее выполнение не будет ожидать. Так что во всех этих случаях текущий поток выполнения приводит кouterScopeVar = undefined
Давайте обсудим каждый пример (я отметил часть, которая вызывается асинхронно или с задержкой для некоторых событий):
1.
Здесь мы регистрируем список событий, который будет выполняться при этом конкретном событии. Здесь происходит загрузка изображения. Затем текущее выполнение продолжается со следующими строками,
img.src = 'lolcat.png';
и в тоalert(outerScopeVar);
же время событие может не произойти. т.е.img.onload
ожидание загрузки указанного изображения асинхронно. Это произойдет во всех следующих примерах - событие может отличаться.2.
Здесь роль тайм-аута играет роль, которая вызовет обработчик по истечении указанного времени. Это так
0
, но все же он регистрирует асинхронное событие, которое будет добавлено в последнюю позициюEvent Queue
для выполнения, что обеспечивает гарантированную задержку.3.
4.
Узел может рассматриваться как король асинхронного кодирования. Здесь отмеченная функция зарегистрирована как обработчик обратного вызова, который будет выполнен после чтения указанного файла.
5.
Очевидное обещание (что-то будет сделано в будущем) является асинхронным. см. В чем разница между отложенным, обещанием и будущим в JavaScript?
https://www.quora.com/Whats-the-difference-between-a-promise-and-a-callback-in-Javascript
источник