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

учитывая следующие примеры, почему 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 во всех этих примерах? Я не хочу обходных путей, я хочу знать!--21-->почему это происходит.


Примечание: это канонический вопрос в JavaScript asynchronicity. Не стесняйтесь улучшать этот вопрос и добавлять более упрощенные примеры, с которыми сообщество может идентифицировать.

6 ответов


одно слово ответ:asynchronicity.

словами

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

  • @Felix Kling "как вернуть ответ от вызова AJAX". См. его отличный ответ, объясняющий синхронные и асинхронные потоки, а также " реструктуризацию раздел кода.
    @Бенджамин Gruenbaum также приложил немало усилий, объясняя asynchronicity в том же потоке.

  • @Мэтт ещ, чтобы "получить данные из ПФ.функцию ReadFile" также объясняет asynchronicity очень хорошо в простой форме.


ответ на вопрос

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

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

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

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

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

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

более конкретно, когда JS engine простаивает - не выполняя стек (a) синхронного кода-он будет опрашивать события, которые могли вызвать асинхронные обратные вызовы (например, истекший тайм-аут, полученный сетевой ответ) и выполнять их один за другим. Это рассматривается как Цикл Обработки Событий.

то есть асинхронный код, выделенный в рисованной красные фигуры могут выполняться только после выполнения всего оставшегося синхронного кода в соответствующих блоках кода:

async code highlighted

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

это просто, на самом деле. Логика, которая зависит от выполнения асинхронной функции, должна быть запущена / вызвана изнутри этой асинхронной функции. Например, перемещение alerts и console.logs тоже внутри функции обратного вызова выведет ожидаемый результат, потому что результат доступен в этот момент.

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

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

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);
}

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

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

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

обещания

хотя есть способы сохранить обратный вызов ад в заливе с vanilla JS обещания растут в популярности и в настоящее время стандартизированы в ES6 (см. обещание-MDN).

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


читать материал о JavaScript asynchronicity

  • искусство узел - вызовы объясняет асинхронный код и обратные вызовы очень хорошо с примеры и узел vanilla JS.код js также.

Примечание: я отметил этот ответ как сообщество Wiki, поэтому любой, у кого есть хотя бы 100 репутаций, может редактировать и улучшать его! Пожалуйста, не стесняйтесь улучшить этот ответ или представить совершенно новый ответ, если вы хотите.

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


ответ Фабрисио на месте; но я хотел дополнить его ответ чем-то менее техническим, который фокусируется на аналогии, чтобы помочь объяснить концепцию асинхронности.


Аналогию...

меня: Привет Боб, мне нужно знать, как мы фу ' d the барна прошлой неделе. Джим хочет получить отчет, и ты единственный, кто знает подробности.

Боб: конечно, но это займет около 30 минут?

меня: это здорово Боб. Перезвони мне, когда получишь информацию!


представьте себе, если бы разговор пошел так;

меня: Привет Боб, мне нужно знать, как мы фу ' d барна прошлой неделе. Джим хочу сообщите об этом, и вы единственный, кто знает подробности об этом.

Боб: конечно, но это займет около 30 минут?

меня: это здорово Боб. Я подожду.


это асинхронное и синхронное поведение

это именно то, что происходит во всех примерах на наш вопрос. Загрузка изображения, загрузка файла с диска и запрос страницы через AJAX-все это медленные операции (в контексте современных вычислений).

, а не ожидание для выполнения этих медленных операций JavaScript позволяет зарегистрировать функцию обратного вызова, которая будет выполнена когда медленная операция завершена. Тем временем JavaScript будет продолжать выполнять другой код. Тот факт, что JavaScript выполняется другой код в ожидании медленной операции для завершения делает поведениеасинхронные. Если бы JavaScript ждал завершения операции Перед выполнением любого другого кода, это было бы синхронно поведение.

var outerScopeVar;    
var img = document.createElement('img');

// Here we register the callback function.
img.onload = function() {
    // Code within this function will be executed once the image has loaded.
    outerScopeVar = this.width;
};

// But, while the image is loading, JavaScript continues executing, and
// processes the following lines of JavaScript.
img.src = 'lolcat.png';
alert(outerScopeVar);

в коде выше, мы спрашиваем JavaScript для загрузки lolcat.png, который является sloooow операции. Функция обратного вызова будет выполнена после завершения этой медленной операции, но в то же время JavaScript будет продолжать обрабатывать следующие строки кода; т. е. alert(outerScopeVar).

вот почему мы видим предупреждение показывает undefined; С alert() обрабатывается немедленно, а не после загрузки изображения.

для того, чтобы исправить наш код, все, что нам нужно сделать, это переместить alert(outerScopeVar) код на функции обратного вызова. Как следствие этого, нам больше не нужны outerScopeVar переменная объявлена как глобальная переменная.

var img = document.createElement('img');

img.onload = function() {
    var localScopeVar = this.width;
    alert(localScopeVar);
};

img.src = 'lolcat.png';

вы всегда см. обратный вызов указан как функция, потому что это единственный* способ в JavaScript определить некоторый код, но не выполнить его до более позднего времени.

поэтому во всех наших примерах function() { /* Do something */ } является обратным вызовом; исправить все примеры, все мы нужно сделать, это переместить код, который нуждается в ответе операции туда!

* технически вы можете использовать eval(), но eval() зло с этой целью


как я могу заставить моего абонента ждать?

в настоящее время у вас может быть код, подобный этому;

function getWidthOfImage(src) {
    var outerScopeVar;

    var img = document.createElement('img');
    img.onload = function() {
        outerScopeVar = this.width;
    };
    img.src = src;
    return outerScopeVar;
}

var width = getWidthOfImage('lolcat.png');
alert(width);

однако, теперь мы знаем, что return outerScopeVar происходит немедленно; перед onload функция обратного вызова обновил переменная. Это приводит к getWidthOfImage() возвращение undefined и undefined будучи предупреждены.

чтобы исправить это, нам нужно разрешить вызов функции getWidthOfImage() чтобы зарегистрировать обратный вызов, затем переместите оповещение ширины, чтобы быть в пределах этого обратного вызова;

function getWidthOfImage(src, cb) {     
    var img = document.createElement('img');
    img.onload = function() {
        cb(this.width);
    };
    img.src = src;
}

getWidthOfImage('lolcat.png', function (width) {
    alert(width);
});

... как и раньше, обратите внимание, что мы смогли удалить глобальные переменные (в этом случае width).


вот более краткий ответ для людей, которые ищут быструю ссылку, а также некоторые примеры использования обещаний и async/await.

начните с наивного подхода( который не работает) для функции, которая вызывает асинхронный метод (в этом случае setTimeout) и возвращает сообщение:

function getMessage() {
  var outerScopeVar;
  setTimeout(function() {
    outerScopeVar = 'Hello asynchronous world!';
  }, 0);
  return outerScopeVar;
}
console.log(getMessage());

undefined регистрируется в этом случае, потому что getMessage возвращает до setTimeout обратный вызов вызывается и обновляется outerScopeVar.

два основных способа для его решения используют обратные вызовы и обещания:

обратные вызовы

изменение здесь в том, что getMessage принимает callback параметр, который будет вызываться для доставки результатов обратно в вызывающий код, как только он будет доступен.

function getMessage(callback) {
  setTimeout(function() {
    callback('Hello asynchronous world!');
  }, 0);
}
getMessage(function(message) {
  console.log(message);
});

обещания

обещает предоставить альтернативу, которая является более гибкой, чем обратные вызовы, потому что они могут быть естественно объединены для координации нескольких асинхронных операций. А Обещания/A+ стандартная реализация изначально предоставляется в узле.js (0.12+) и многие текущие браузеры, но также реализованы в библиотеках, таких как птица и Q.

function getMessage() {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve('Hello asynchronous world!');
    }, 0);
  });
}

getMessage().then(function(message) {
  console.log(message);  
});

jQuery Deferreds

jQuery обеспечивает функциональность, которая похожа на обещания с его Отложенный.

function getMessage() {
  var deferred = $.Deferred();
  setTimeout(function() {
    deferred.resolve('Hello asynchronous world!');
  }, 0);
  return deferred.promise();
}

getMessage().done(function(message) {
  console.log(message);  
});

async / await

если ваша среда JavaScript включает поддержку async и await (как узел.js 7.6+), то вы можете использовать обещания синхронно в async функции:

function getMessage () {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve('Hello asynchronous world!');
        }, 0);
    });
}

async function main() {
    let message = await getMessage();
    console.log(message);
}

main();

чтобы заявить очевидное, чашка представляет outerScopeVar.

асинхронные функции, как...

asynchronous call for coffee


другие ответы превосходны, и я просто хочу дать прямой ответ на это. Просто ограничение асинхронных вызовов jQuery

все вызовы ajax (включая $.get или $.post или $.ajax) являются асинхронными.

учитывая ваш пример

var outerScopeVar;  //line 1
$.post('loldog', function(response) {  //line 2
    outerScopeVar = response;
});
alert(outerScopeVar);  //line 3

выполнение кода начинается с строки 1, объявляет переменную и триггеры и асинхронный вызов в строке 2, (т. е. запрос post), и он продолжает свое выполнение из строки 3, Без ожидание запроса post для завершения его выполнения.

скажем, что запрос post занимает 10 секунд для завершения, значение outerScopeVar будет установлен только после этих 10 секунд.

чтобы попробовать,

var outerScopeVar; //line 1
$.post('loldog', function(response) {  //line 2, takes 10 seconds to complete
    outerScopeVar = response;
});
alert("Lets wait for some time here! Waiting is fun");  //line 3
alert(outerScopeVar);  //line 4

теперь, когда вы выполняете это, вы получите предупреждение в строке 3. Теперь подождите некоторое время, пока вы не убедитесь, что запрос post вернул некоторое значение. Затем при нажатии кнопки ОК в поле оповещение следующее оповещение будет печатать ожидаемое значение, так как вы ждать его.

в реальной жизни сценарий, код становится,

var outerScopeVar;
$.post('loldog', function(response) {
    outerScopeVar = response;
    alert(outerScopeVar);
});

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


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

давайте обсудим каждый пример(я отметил часть, которая вызывается асинхронно или задерживается для некоторых событий occur):

1.

enter image description here

здесь мы регистрируем eventlistner, который будет выполняться при этом конкретном событии.Здесь загрузка изображения.Тогда текущее выполнение непрерывное со следующими строками img.src = 'lolcat.png'; и alert(outerScopeVar); между тем, событие может и не произойти. я.е, фунции img.onload дождитесь загрузки указанного изображения, asynchrously. Это произойдет во всем следующем примере-событие может отличаться.

2.

2

здесь событие timeout играет роль, которая вызовет обработчик после указанного времени. Вот он 0, но все же он регистрирует асинхронное событие, которое будет добавлено в последнюю позицию Event Queue для исполнения, что делает гарантированные задержки.

3.

enter image description here На этот раз "Аякс" обратный звонок.

4.

enter image description here

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

5.

enter image description here

очевидно, обещать (что-то будет сделано в будущем) является асинхронным. см.в чем разница между Отложено, обещание и будущее в JavaScript?

https://www.quora.com/Whats-the-difference-between-a-promise-and-a-callback-in-Javascript