对于下面的例子,为什么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
这里有一个更简洁的答案,给那些正在寻找快速参考的人,以及一些使用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();
简单的答案是:异步性。
为什么需要异步?
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(消息);
});