对于下面的例子,为什么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);
// with observables
var outerScopeVar;
myObservable.subscribe(function (value) {
    outerScopeVar = value;
});
console.log(outerScopeVar);
// geolocation API
var outerScopeVar;
navigator.geolocation.getCurrentPosition(function (pos) {
    outerScopeVar = pos;
});
console.log(outerScopeVar);

为什么在所有这些例子中输出都是未定义的?我不想要变通办法,我想知道为什么会这样。


注意:这是JavaScript异步性的典型问题。请随意改进这个问题,并添加更多社区可以认同的简化示例。


当前回答

其他的答案都很好,我只是想提供一个直接的答案。仅限于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请求已经返回了一些值。然后,当您单击警报框上的OK时,下一个警报将打印预期值,因为您一直在等待它。

在现实生活中,代码会变成,

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

依赖于异步调用的所有代码都被移动到异步块中,或者通过等待异步调用。

其他回答

在所有这些场景中,outerScopeVar都是异步修改或分配值的,或者在稍后发生(等待或监听某些事件的发生),当前执行将不会等待这些事件。所有这些情况下,当前执行流的结果都是outerScopeVar = undefined

让我们来讨论每个例子(我标记了被称为异步或延迟发生某些事件的部分):

1.

这里我们注册了一个eventlistner,它将在特定事件时执行。这里加载图像。然后,当前执行继续与下一行img。SRC = 'lolcat.png';和警报(outerScopeVar);与此同时,事件可能不会发生。也就是函数img。Onload等待被引用的图像异步加载。这将发生在所有下面的例子-事件可能不同。

2.

在这里,超时事件起作用,它将在指定的时间之后调用处理程序。这里它是0,但它仍然注册了一个异步事件,它将被添加到事件队列的最后一个位置执行,这使得有保证的延迟。

3.

这次是ajax回调。

4.

节点可以被认为是异步编码的王者。这里标记的函数被注册为回调处理程序,该处理程序将在读取指定文件后执行。

5.

显而易见的承诺(将来会做某事)是异步的。JavaScript中Deferred, Promise和Future的区别是什么?

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

答案只有一个字:异步性。

前言

这个主题在Stack Overflow中已经被迭代了至少几千次。因此,首先我想指出一些非常有用的资源:

@Felix Kling对“如何从异步调用返回响应?”的回答。请参阅他解释同步和异步流的精彩回答,以及“重构代码”一节。 @Benjamin Gruenbaum也花了很多精力来解释同一个线程中的异步性。 @Matt Esch对“从fs获取数据”的回答。readFile也以简单的方式很好地解释了异步性。


手头问题的答案

让我们先追溯一下常见的行为。在所有示例中,outerScopeVar都是在函数内部修改的。该函数显然没有立即执行;它被赋值或作为参数传递。这就是我们所说的回调。

问题是,这个回调什么时候被调用?

这要看情况而定。让我们再试着追踪一些常见的行为:

img.onload may be called sometime in the future when (and if) the image has successfully loaded. setTimeout may be called sometime in the future after the delay has expired and the timeout hasn't been canceled by clearTimeout. Note: even when using 0 as delay, all browsers have a minimum timeout delay cap (specified to be 4ms in the HTML5 spec). jQuery $.post's callback may be called sometime in the future when (and if) the Ajax request has been completed successfully. Node.js's fs.readFile may be called sometime in the future when the file has been read successfully or thrown an error.

在所有情况下,我们都有一个可能在未来某个时候运行的回调。这个“将来的某个时候”就是我们所说的异步流。

异步执行被推出同步流。也就是说,在同步代码堆栈执行时,异步代码永远不会执行。这就是JavaScript是单线程的含义。

更具体地说,当JS引擎空闲时——不执行一堆(a)同步代码——它将轮询可能触发异步回调的事件(例如超时,收到的网络响应),并一个接一个地执行它们。这被视为事件循环。

也就是说,在手绘的红色图形中突出显示的异步代码只有在各自代码块中所有剩余的同步代码执行完之后才能执行:

简而言之,回调函数是同步创建的,但异步执行。在知道异步函数已经执行之前,您不能依赖它的执行,如何做到这一点呢?

这真的很简单。依赖于异步函数执行的逻辑应该从这个异步函数内部启动/调用。例如,移动警报和控制台。回调函数内的日志将输出预期的结果,因为结果在那时是可用的。

实现自己的回调逻辑

通常,您需要对异步函数的结果做更多的事情,或者根据异步函数被调用的位置对结果做不同的事情。让我们来看一个更复杂的例子:

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 an 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 an 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 an 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实现)。您只需要传递回调函数,并理解它将在同步流之外执行,并重新构造代码以适应这一点。

您还将注意到,由于异步特性,不可能将一个值从异步流返回到定义回调的同步流,因为异步回调是在同步代码已经完成执行很久之后才执行的。

您将不得不使用回调模式,而不是从异步回调返回值,或者……承诺。

承诺

虽然在普通JS中有一些方法可以避免回调的麻烦,但是承诺越来越受欢迎,目前正在ES6中标准化(参见承诺- MDN)。

promise(又名Futures)提供了更线性的异步代码阅读,因此更令人愉快,但是解释它们的全部功能超出了这个问题的范围。相反,我将把这些优秀的资源留给感兴趣的人:

JavaScript承诺- HTML5岩石 你错过了承诺的意义- domenic.me


更多关于JavaScript异步性的阅读材料

Node的艺术-回调用普通JS示例和Node. JS代码很好地解释了异步代码和回调。


注意:我已经把这个答案标记为社区维基。因此,任何拥有至少100个声誉的人都可以编辑和改进它!如果您愿意,请随时改进这个答案或提交一个全新的答案。 我想把这个问题变成一个规范的主题,以回答与Ajax无关的异步性问题(如何从Ajax调用返回响应?为此),因此这个话题需要你的帮助,尽可能地好和有用!

其他的答案都很好,我只是想提供一个直接的答案。仅限于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请求已经返回了一些值。然后,当您单击警报框上的OK时,下一个警报将打印预期值,因为您一直在等待它。

在现实生活中,代码会变成,

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

依赖于异步调用的所有代码都被移动到异步块中,或者通过等待异步调用。

简单的答案是:异步性。

为什么需要异步?

JavaScript是单线程的,这意味着脚本的两个部分不能同时运行;他们不得不一个接一个地跑。在浏览器中,JavaScript与不同浏览器的其他内容共享一个线程。但通常JavaScript与绘制、更新样式和处理用户操作(比如突出显示文本和与表单控件交互)处于同一队列中。其中一种物质的活性会延迟其他物质的活性。

您可能已经使用事件和回调来解决这个问题。以下是一些活动:

var img1 = document.querySelector('.img-1'); img1。addEventListener('load', function() { //图像加载 console.log(“加载”); }); img1。addEventListener('错误',函数(){ //错误 console.log(“错误打印”); }); <img class="img-1" src="#" alt="img">

这一点也不打喷嚏。我们获得图像,添加两个侦听器,然后JavaScript可以停止执行,直到其中一个侦听器被调用。

不幸的是,在上面的例子中,事件可能发生在我们开始监听它们之前,所以我们需要使用图像的"complete"属性来解决这个问题:

var img1 = document.querySelector('.img-1'); 函数loaded() { //图像加载 console.log(“加载”); } If (img1.complete) { 加载(); }其他{ img1。addEventListener(加载,加载); } img1。addEventListener('错误',函数(){ //错误 console.log(“错误打印”); }); <img class="img-1" src="#" alt="img">

这不会在我们有机会监听之前捕捉到错误的图像;不幸的是,DOM没有提供这样做的方法。另外,这是加载一张图像。如果我们想知道一组图像何时加载,事情会变得更加复杂。

事情并不总是最好的

事件是伟大的事情,可以发生在同一对象上多次- keyup, touchstart等。对于这些事件,您并不真正关心在连接侦听器之前发生了什么。

正确的方法有两种:回调和承诺。

回调

回调函数是在其他函数的参数中传递的函数,这个过程在JavaScript中是有效的,因为函数是对象,对象可以作为参数传递给函数。回调函数的基本结构是这样的:

函数getMessage(回调){ 回调(); } 函数showMessage() { console.log(“Hello world !我是一个回调”); } getMessage (showMessage);

承诺

尽管在普通JS中有一些方法可以避免回调的麻烦,但是承诺越来越受欢迎,目前正在ES6中标准化(参见承诺)。

promise是一个占位符,表示异步操作的最终结果(值)

承诺占位符将被结果值(如果成功)或失败原因(如果不成功)所取代。

如果你不需要知道某事何时发生,而只需要知道它是否发生,那么承诺就是你要寻找的。

promise有点像一个事件监听器,除了:

一个承诺只能成功或失败一次 一个承诺不能从失败转变为成功,反之亦然 一旦你有了结果,承诺是不可改变的 如果承诺成功或失败,并且稍后添加了成功/失败回调,则将调用正确的回调 事件是否发生在您添加回调之前并不重要

注意:总是返回Promise内部函数的结果,否则后续函数就没有作用了。

承诺的术语

承诺可以是:

fulfilled: The action relating to the promise succeeded the asynchronous operation has completed the promise has a value the promise will not change again rejected: The action relating to the promise failed the asynchronous operation failed the promise will never be fulfilled the promise has a reason indicating why the operation failed the promise will not change again pending: Hasn't fulfilled or rejected yet the asynchronous operation hasn't been completed yet can transition to fulfilled or rejected settled: Has been fulfilled or rejected and is thus immutable

如何创造承诺

函数getMessage() { 返回新的Promise(函数(解析,拒绝){ setTimeout(函数(){ 解决(“Hello world !”我是一个承诺’); }, 0); }); } getMessage(),然后(函数(消息){ console.log(消息); });

这里有一个更简洁的答案,给那些正在寻找快速参考的人,以及一些使用promises和async/await的例子。

从一个调用异步方法(在本例中是setTimeout)并返回一条消息的函数的朴素方法(这不起作用)开始:

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

在这种情况下,将记录undefined,因为getMessage在调用setTimeout回调并更新outerScopeVar之前返回。

解决它的两种主要方法是使用回调和承诺:

回调

这里的变化是getMessage接受一个回调参数,一旦结果可用,将调用该参数将结果交付回调用代码。

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

承诺

承诺提供了一种比回调更灵活的替代方法,因为它们可以自然地组合在一起来协调多个异步操作。promise /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通过它的Deferreds提供了类似承诺的功能。

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

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

异步- await

如果你的JavaScript环境包含对async和await的支持(比如Node.js 7.6+),那么你可以在async函数中同步使用promises:

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