你的前两个例子基本上是等价的。但是,你猜怎么着,你不能在这两个例子中使用for 循环。在for 循环中模拟await 语句没有一种简单的编码方式。这是因为for 循环(或while 循环)与async/await 的交互比在您的两个示例中插入简单的.then() 语句更先进。
await 暂停整个包含函数的执行。这甚至会暂停 for 和 while 循环。要仅使用 .then() 进行类似的编程,您必须发明自己的循环结构,因为仅使用 for 循环是无法做到的。
例如,如果您使用await:
let asyncFunc = function() {
return new Promise((resolve,reject) => {
setTimeout(_ => resolve('This is async'), 1000)
})
}
async function someFunc() {
for(let i = 0; i < 5; i++) {
console.log('This is sync')
console.log(await asyncFunc())
}
}
而且,如果您想仅使用.then() 进行类比,则不能使用for 循环,因为没有await 就无法“暂停”它。相反,您必须设计自己的循环,这通常涉及调用本地函数并维护自己的计数器:
function someFunc() {
let i = 0;
function run() {
if (i++ < 5) {
console.log('This is sync')
asyncFunc().then(result => {
console.log(result);
run();
});
}
}
run();
}
或者,有些人使用 .reduce() 这样的构造(尤其是在迭代数组时):
// sequence async calls iterating an array
function someFunc() {
let data = [1,2,3,4,5];
return data.reduce((p, val) => {
console.log('This is sync')
return p.then(() => {
return asyncFunc().then(result => {
console.log(result);
});
});
}, Promise.resolve());
}
谁能解释一下有什么区别,或者换句话说,async/await 在幕后到底做了什么?
在幕后,Javascript 解释器会在 await 语句处暂停函数的进一步执行。保存当前的函数上下文(局部变量状态、执行点等),从函数返回一个承诺,并在该函数之外继续执行,直到稍后某个时间等待被等待的承诺解决或拒绝。如果它解决了,则选择相同的执行状态,并且该函数继续执行更多直到下一个 await 等等,直到最终该函数没有更多的 await 语句并且没有更多要执行(或到达return 声明)。
为了进一步解释,这里有一些关于异步函数的背景:
当您声明 async 函数时,解释器会创建一种始终返回承诺的特殊函数。如果函数显式返回一个被拒绝的 Promise 或函数中存在异常(异常被解释器捕获并更改为对函数返回的 Promise 的拒绝),则该 Promise 将被拒绝。
如果/当函数返回一个正常值或只是通过完成其执行正常返回时,承诺就会被解决。
当您调用该函数时,它会像任何普通函数一样开始同步执行。这包括函数中可能存在的任何循环(如示例中的 for 循环)。一旦函数遇到第一个await 语句并且它正在等待的值是一个promise,那么函数就会在该点返回并返回它的promise。该功能的进一步执行被暂停。由于该函数返回其承诺,因此该函数调用之后的任何代码都会继续运行。稍后,当 async 函数内部等待的承诺解决时,将在事件循环中插入一个事件以恢复该函数的执行。当解释器返回事件循环并到达该事件时,函数的执行将在await 语句之后的行上继续执行。
如果有任何其他 await 语句正在等待 Promise,那么它们同样会导致函数执行暂停,直到正在等待的 Promise 被解决或拒绝。
这种在过程中暂停或暂停函数执行的能力的美妙之处在于它可以在循环结构中工作,例如for 和while,这使得使用这样的循环按顺序执行异步操作非常重要在我们有 async 和 await 之前(正如您所发现的),更容易编程。仅使用.then() 来编写排序循环并没有简单的类似方法。有些使用.reduce(),如上所示。其他人使用内部函数调用,如上所示。