对于下面的例子,为什么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异步性的典型问题。请随意改进这个问题,并添加更多社区可以认同的简化示例。


当前回答

在所有这些场景中,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

其他回答

在所有这些场景中,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

这里有一个更简洁的答案,给那些正在寻找快速参考的人,以及一些使用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();

显而易见,杯子代表outerScopeVar。

异步函数就像……

Fabrício的答案是正确的;但我想用一些不那么技术性的东西来补充他的回答,侧重于一个类比来帮助解释异步性的概念。


一个类比……

昨天,我正在做的工作需要从一位同事那里得到一些信息。我给他打了电话;对话是这样进行的:

我:嗨,鲍勃,我想知道上周我们是怎么在酒吧吃饭的。吉姆想要一份关于它的报告,而你是唯一知道细节的人。

当然可以,但是大概要30分钟吧?

我:太好了,鲍勃。你得到消息后给我回个电话!

这时,我挂了电话。因为我需要鲍勃提供一些信息来完成我的报告,所以我放下报告去喝了杯咖啡,然后又处理了一些电子邮件。40分钟后(Bob反应慢),Bob给我回了电话,告诉了我所需要的信息。在这一点上,我继续我的工作和我的报告,因为我有所有我需要的信息。


想象一下,如果谈话是这样进行的;

我:嗨,鲍勃,我想知道上周我们是怎么在酒吧吃饭的。吉姆想要一份关于它的报告,而你是唯一知道细节的人。

当然可以,但是大概要30分钟吧?

我:太好了,鲍勃。我将等待。

我坐在那里等着。等着。等着。40分钟。除了等待什么都不做。最后,鲍勃给了我信息,我们挂了电话,我完成了我的报告。但我失去了40分钟的工作效率。


这是异步与同步行为

这正是我们问题中所有例子所发生的。加载图像、从磁盘加载文件以及通过AJAX请求页面都是缓慢的操作(在现代计算环境中)。

Rather than waiting for these slow operations to complete, JavaScript lets you register a callback function which will be executed when the slow operation has completed. In the meantime, however, JavaScript will continue to execute other code. The fact that JavaScript executes other code whilst waiting for the slow operation to complete makes the behaviorasynchronous. Had JavaScript waited around for the operation to complete before executing any other code, this would have been synchronous behavior.

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,这是一个缓慢的操作。回调函数将在这个缓慢的操作完成后执行,但与此同时,JavaScript将继续处理下一行代码;即警报(outerScopeVar)。

这就是为什么我们看到警报显示为undefined;因为alert()是立即处理的,而不是在图像加载之后。

为了修复代码,我们所要做的就是将警报(outerScopeVar)代码移动到回调函数中。因此,我们不再需要将outerScopeVar变量声明为全局变量。

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

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

img.src = 'lolcat.png';

你总是会看到一个回调被指定为一个函数,因为这是JavaScript中定义一些代码,但直到以后才执行它的唯一方法。

因此,在我们所有的例子中,函数(){/* 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);

然而,我们现在知道返回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)。

其他的答案都很好,我只是想提供一个直接的答案。仅限于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);
});

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