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

Вопрос или проблема

В приведенных ниже примерах, почему outerScopeVar неопределен во всех случаях?

var outerScopeVar;

var img = document.createElement('img');
img.onload = function() {
    outerScopeVar = this.width;
};
img.src="https://stackoverflow.com/questions/79168027/lolcat.png";
alert(outerScopeVar);
var outerScopeVar;
setTimeout(function() {
    outerScopeVar="Hello Asynchronous World!";
}, 0);
alert(outerScopeVar);
// Пример с использованием jQuery
var outerScopeVar;
$.post('loldog', function(response) {
    outerScopeVar = response;
});
alert(outerScopeVar);
// Пример Node.js
var outerScopeVar;
fs.readFile('./catdog.html', function(err, data) {
    outerScopeVar = data;
});
console.log(outerScopeVar);
// с промисами
var outerScopeVar;
myPromise.then(function (response) {
    outerScopeVar = response;
});
console.log(outerScopeVar);
// с observable
var outerScopeVar;
myObservable.subscribe(function (value) {
    outerScopeVar = value;
});
console.log(outerScopeVar);
// геолокация API
var outerScopeVar;
navigator.geolocation.getCurrentPosition(function (pos) {
    outerScopeVar = pos;
});
console.log(outerScopeVar);

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


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

Ответ в одно слово: асинхронность.

Введение

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


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

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

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

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

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

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

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

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

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

асинхронный код выделен

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

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

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

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

var outerScopeVar;
helloCatAsync();
alert(outerScopeVar);

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

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

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

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

// 1. Вызов helloCatAsync, передавая функцию обратного вызова,
//    которая будет вызвана, получив результат от асинхронной операции
helloCatAsync(function(result) {
    // 5. Получили результат от асинхронной функции,
//    теперь делайте с ним все, что хотите:
    alert(result);
});

// 2. Параметр "callback" является ссылкой на функцию, которая
//    была передана как аргумент в вызове helloCatAsync
function helloCatAsync(callback) {
    // 3. Запустите асинхронную операцию:
    setTimeout(function() {
        // 4. Завершена асинхронная операция,
//    вызовите обратный вызов, передавая результат как аргумент
        callback('Nya');
    }, Math.random() * 2000);
}

Кодовый фрагмент приведенного выше примера:

// 1. Вызов helloCatAsync, передавая функцию обратного вызова,
//    которая будет вызвана, получив результат от асинхронной операции
console.log("1. функция вызвана...")
helloCatAsync(function(result) {
    // 5. Получили результат от асинхронной функции,
//    теперь делайте с ним все, что хотите:
    console.log("5. результат: ", result);
});

// 2. Параметр "callback" является ссылкой на функцию, которая
//    была передана как аргумент в вызове helloCatAsync
function helloCatAsync(callback) {
    console.log("2. обратный вызов здесь — это функция, переданная как аргумент выше...")
    // 3. Запустите асинхронную операцию:
    setTimeout(function() {
    console.log("3. начинается асинхронная операция...")
    console.log("4. асинхронная операция завершена, вызываем обратный вызов, передавая результат...")
        // 4. Завершена асинхронная операция,
//    вызовите обратный вызов, передавая результат как аргумент
        callback('Nya');
    }, Math.random() * 2000);
}

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

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

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

Промисы

Хотя существуют способы держать ад обратных вызовов под контролем с помощью чистого JS, промисы становятся все более популярными и в настоящее время стандартизируются в ES6 (см. Promise – MDN).

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


Дополнительные материалы о асинхронности JavaScript


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

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

</div><div class="s-prose js-post-body" itemprop="text">

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


Аналогия…

Вчера работа, которую я выполнял, требовала некоторой информации от коллеги. Я ему позвонил; вот как прошел разговор:

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

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

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

На этом я положил трубку. Поскольку мне нужна была информация от Боба, чтобы завершить отчет, я оставил отчет и пошел за кофе вместо этого, затем проверил некоторые электронные письма. Через 40 минут (Боб не торопится), Боб перезвонил и дал мне информацию, которую я искал. В этот момент я возобновил свою работу над отчетом, так как у меня была вся информация.


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

Я: Привет, Боб, мне нужно узнать, как мы foo‘d bar‘d на прошлой неделе. Джим хочет отчет, и ты единственный, кто знает детали.

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

Я: Это замечательно, Боб. Я подожду.

И я сидел и ждал. Ждал. Ждал. Целых 40 минут. Ничего не делая, кроме как ждал. Наконец, Боб дал мне информацию, мы попрощались, и я завершил свой отчет. Но я потерял 40 минут продуктивности.


Это асинхронное против синхронного поведения

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

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

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

// Здесь мы регистрируем функцию обратного вызова.
img.onload = function() {
    // Код внутри этой функции будет выполнен, как только изображение загрузится.
    outerScopeVar = this.width;
};

// Но, пока изображение загружается, JavaScript продолжает выполнение и
// обрабатывает следующие строки JavaScript.
img.src="https://stackoverflow.com/questions/79168027/lolcat.png";
alert(outerScopeVar);

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

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

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

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

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

img.src="https://stackoverflow.com/questions/79168027/lolcat.png";

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

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

* Технически, вы также можете использовать 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("https://stackoverflow.com/questions/79168027/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("https://stackoverflow.com/questions/79168027/lolcat.png", function (width) {
    alert(width);
});

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

</div><div class="s-prose js-post-body" itemprop="text">

Вот более краткий ответ для людей, которые ищут быстрое справочное руководство, а также некоторые примеры с использованием промисов и 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);
});

Промисы

Промисы предоставляют альтернативу, которая более гибкая, чем обратные вызовы, потому что их можно естественно комбинировать для координации нескольких асинхронных операций. Стандартное исполнение Promises/A+ предоставляется нативно в node.js (0.12+) и многих современных браузерах, но также реализуется в таких библиотеках, как Bluebird и Q.

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

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

jQuery Отложенные объекты

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 (например, Node.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();
</div><div class="s-prose js-post-body" itemprop="text">

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

Асинхронные функции такие…

асинхронный запрос на кофе

</div><div class="s-prose js-post-body" itemprop="text">

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

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

Рассмотрим ваш пример

var outerScopeVar;  //строка 1
$.post('loldog', function(response) {  //строка 2
    outerScopeVar = response;
});
alert(outerScopeVar);  //строка 3

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

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

Попробуем так:

var outerScopeVar; //строка 1
$.post('loldog', function(response) {  //строка 2, занимает 10 секунд для завершения
    outerScopeVar = response;
});
alert("Давайте подождем немного! Ожидание - это весело");  //строка 3
alert(outerScopeVar);  //строка 4

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

В реальной жизни код становится таким:

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

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

</div><div class="s-prose js-post-body" itemprop="text">

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

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

1.

введите описание изображения здесь

Здесь мы регистрируем слушателя события, который будет выполнен после наступления этого определенного события, здесь — загрузки изображения. Затем текущее выполнение продолжается следующими строками img.src="https://stackoverflow.com/questions/79168027/lolcat.png"; и alert(outerScopeVar);, в то время как событие может еще не произойти. То есть функция img.onload ждет, пока загружается упомянутое изображение, асинхронно. Это произойдет во всех следующих примерах — событие может отличаться.

2.

2

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

3.

введите описание изображения здесь
Этот раз — обратный вызов ajax.

4.

введите описание изображения здесь

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

5.

введите описание изображения здесь

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

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

</div><div class="s-prose js-post-body" itemprop="text">

Короткий ответ: асинхронность.

Зачем нужна асинхронность?

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

Вы, вероятно, использовали события и обратные вызовы, чтобы обойти это. Вот события:

var img1 = document.querySelector('.img-1');

img1.addEventListener('load', function() {
  // изображение загружено
  console.log("Загружено");
});

img1.addEventListener('error', function() {
  // ошибка поймана
  console.log("Ошибка выведена");
});
<img class="img-1" src="#" alt="img">

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

К сожалению, в приведенном выше примере возможно, что события произошли до того, как мы начали за ними следить, поэтому нам нужно обойти это, используя свойство “complete” изображений:

var img1 = document.querySelector('.img-1');

function loaded() {
  // изображение загружено
  console.log("Загружено");
}

if (img1.complete) {
  loaded();
} else {
  img1.addEventListener('load', loaded);
}

img1.addEventListener('error', function() {
  // ошибка поймана
  console.log("Ошибка выведена");
});
<img class="img-1" src="#" alt="img">

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

События не всегда лучший способ

События отлично подходят для вещей, которые могут происходить несколько раз с одним и тем же объектом — keyup, touchstart и т.д. С такими событиями вы действительно не заботитесь о том, что произошло до того, как вы добавили слушатель.

Два основных способа сделать это правильно — обратные вызовы и промисы.

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

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

function getMessage(callback) {
  callback();
}

function showMessage() {
  console.log("Привет, мир! Я — обратный вызов");
}
getMessage(showMessage);

Промис

Хотя существуют способы держать ад обратных вызовов на расстоянии с помощью чистого JS, промисы становятся все более популярными и в настоящее время стандартизируются в ES6 (см. Promise).

промис — это заполнитель, представляющий конечный результат (значение) асинхронной операции

  • заполнитель промиса будет заменен значением результата (в случае успеха) или причиной неудачи (в случае неудачи)

Если вам не нужно знать, когда что-то произошло, а просто нужно знать, произошло это или нет, тогда промис — это то, что вам нужно.

Промис немного как слушатель событий, за исключением:

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

Примечание: всегда возвращайте результат из функции внутри промиса, иначе у последующей функции не будет на чем действовать.

Терминология промисов

Промис может быть:

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

Как создать промис

function getMessage() {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve('Привет, мир! Я — промис');
    }, 0);
  });
}

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

Ответ или решение

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

Что происходит в примерах?

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

  1. Использование события загрузки изображения:

    var outerScopeVar;
    
    var img = document.createElement('img');
    img.onload = function() {
       outerScopeVar = this.width;
    };
    img.src="https://stackoverflow.com/questions/79168027/lolcat.png";
    alert(outerScopeVar);

    В этом примере, функция img.onload является асинхронной. Эта функция будет вызвана только после полной загрузки изображения. Тем временем, код продолжает выполняться, и alert(outerScopeVar) срабатывает до того, как загружается изображение, поэтому outerScopeVar не получает значение и остается undefined.

  2. Использование setTimeout:

    var outerScopeVar;
    setTimeout(function() {
       outerScopeVar = "Hello Asynchronous World!";
    }, 0);
    alert(outerScopeVar);

    Здесь setTimeout создаёт асинхронный вызов, который будет выполнен лишь после того, как основной стек вызовов будет пуст. Код продолжает выполняться, и alert(outerScopeVar) показывает undefined, поскольку присвоение значения произойдет позже.

  3. Асинхронные операции с jQuery:

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

    К тому моменту, когда срабатывает alert, HTTP-запрос может еще не завершиться. Поэтому до завершения этого асинхронного процесса переменная остается undefined.

  4. Чтение файлов в Node.js:

    var outerScopeVar;
    fs.readFile('./catdog.html', function(err, data) {
       outerScopeVar = data;
    });
    console.log(outerScopeVar);

    Функция fs.readFile также выполняется асинхронно, и console.log(outerScopeVar) срабатывает до того, как файл будет прочитан.

  5. Работа с Promise и Observable:

    var outerScopeVar;
    myPromise.then(function(response) {
       outerScopeVar = response;
    });
    console.log(outerScopeVar);

    Как и в других примерах, обработчик then будет выполнен только после того, как Promise будет выполнен, что произойдет позже, чем выполнение console.log(outerScopeVar).

Основная причина: асинхронность

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

Заключение

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

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

Оцените материал
Добавить комментарий

Капча загружается...