[英]Javascript async/await behaviour explanation
我一直认为Javascipt的async / await只是糖语法,例如编写这种和平的代码
let asyncVar = await AsyncFunc()
console.log(asyncVar)
//rest of code with asyncVar
相当于写这个
AsyncFunc().then(asyncVar => {
console.log(asyncVar)
//rest of the code with asyncVar
})
特别是因为您可以在某个随机的,非承诺的对象上调用await,并且它将尝试调用then()函数
但是,我尝试了这种代码的安宁
let asyncFunc = function() {
return new Promise((resolve,reject) => {
setTimeout(_ => resolve('This is async'), 1000)
})
}
for(let i = 0; i < 5; i++) {
console.log('This is sync')
asyncFunc().then(val => console.log(val))
}
for(let i = 0; i < 5; i++) {
console.log('This is sync')
console.log(await asyncFunc())
}
如预期的那样,第一个循环输出“ This is sync” 5次,然后输出“ This is async” 5次。 第二个循环输出“ This is sync”,“ This is async”,“ This is sync”,“ This is async”等。
有人可以解释一下两者之间的区别是什么,换句话说,异步/等待在幕后究竟做了什么?
您的前两个示例基本上是等效的。 但是,你猜怎么着,在这两个示例中不能使用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());
}
有人可以解释一下两者之间的区别是什么,换句话说,异步/等待在幕后究竟做了什么?
在后台,Javascript解释器在await语句中暂停函数的进一步执行。 保存当前函数上下文(局部变量状态,执行点等),从函数返回承诺,并在该函数之外继续执行,直到稍后某个时间之前正在等待的承诺被解析或拒绝。 如果解析,则将选择相同的执行状态,并且该函数将继续执行更多操作,直到下一次await
,依此类推,直到最终该函数不再有await
语句并且不再有执行(或return
语句)为止。
为了进一步说明,这是异步函数的一些背景知识:
声明async
函数时,解释器会创建一种特殊类型的函数,该函数始终返回promise。 如果函数显式返回拒绝的promise或函数中存在异常(解释器捕获该异常并将其更改为对该函数返回的promise的拒绝),则该promise将被拒绝。
如果/当函数返回正常值或只是通过完成其执行而正常返回时,将解决promise。
当您调用该函数时,它开始像任何普通函数一样同步执行。 这包括函数中可能存在的任何循环(如示例中的for
循环)。 一旦函数遇到第一个await
语句,并且它所等待的值是一个promise,则函数将在该点返回并返回其promise。 该功能的进一步执行被暂停。 由于该函数返回其诺言,因此该函数调用之后的所有代码将继续运行。 稍后某个时间,当async
函数内部等待的诺言得以解决时, async
一个事件插入事件循环以恢复该函数的执行。 当解释器返回到事件循环并到达该事件时,将继续执行该函数,该函数先前在await
语句之后的行中停止。
如果还有其他等待诺言的await
语句,则它们将类似地导致函数执行被挂起,直到正在等待的诺言得到解决或拒绝为止。
这种在中途暂停或暂停执行功能的能力的优点在于,它可以在for
和while
类的循环结构中for
,并且使得使用这种循环依次执行异步操作比以前编写起来容易得多。 async
并await
(如您所见)。 没有简单的类似的方法仅使用.then()
来编码序列循环。 如上所示,有些使用.reduce()
。 其他人也使用内部函数调用,如上所示。
异步/等待是下一代的承诺。 基本上,await将保留其余代码的执行,直到await解析为止。 例如。
async function getUser() {
const id = 'zzzz';
const user = await get(`http://getuser.xxx/{id}`);
console.log(user);
}
执行get函数后,唯一的控制台被打印出来。
await
会按照包装盒上的说明进行操作:等待异步函数的响应:
let asyncFunc = () =>
new Promise((resolve,reject) => {
setTimeout(() => resolve("This is async"), 5000)
});
console.log("before await");
console.log(await asyncFunc());
console.log("after await");
这将记录为"before await"
,然后在5秒钟后记录为"This is async"
,随后为"after await"
。
它基本上使异步功能同步运行。
孤立地讲,是的async/await
是诺言的语法糖。 您缺少更大的上下文:承诺本身就在那儿,因为JS无法在await
到来之前暂停函数的执行。 为了模拟这种暂停效果,您必须转换整个同步函数(在本例中for
循环),而不仅仅是转换await
行。
首先,我们必须展开了for
循环成while
(说的语法糖!),那么我们可以把它改写成递归(因为前await
,我们只能“停”功能之间,而不是在他们里面),那么我们就可以重新连接仅在兑现诺言时递归。 这是从async/await
转换为promises时的代码:
let asyncFunc = function() { return new Promise((resolve, reject) => { setTimeout(() => resolve('This is async'), 1000) }); }; function loop(i) { if (i < 5) { console.log('This is sync'); return asyncFunc().then(result => { console.log(result); return loop(++i); }); } } loop(0);
现在,您基本上拥有两种功能: async
功能和非async
功能,并且只有async
功能可以在其中await
(或者您会收到“ SyntaxError:await仅在异步功能中有效”)。 这是一种原因:为了将await
语义“重写”为非await
语义,您需要更改整个函数,而不仅仅是更改await
行。 它是一种语法糖,但在整个功能级别上,而不是在OP示例中的语句级别上。
编辑:您还可以基本上读取原始代码中需要完成的操作:
for(let i = 0; i < 5; i++) {
console.log('This is sync')
console.log(await asyncFunc())
}
让循环从
i
为0
开始。 检查i
是否小于5
; 如果是这样,请记录“ This is sync”,并调用asyncFunc
(并等待其完成)。 然后 ,打印其结果,增加计数器并继续循环。
尽管代码在JavaScript语法方面进行了大幅度的重新排列,但听起来还是很像我写的内容,不是吗? :)
async/await
可能是语法糖,但不仅仅是then()
周围的糖。 async/await
更好的类比是生成器+承诺。 如果编写生成器,您会注意到语法与async/await
几乎相同,但是使用yield
而不是await。
例如,您可以编写一个看起来几乎与第二个for
循环完全一样的生成器,它将产生相同的行为:
let asyncFunc = function() { return new Promise((resolve,reject) => { setTimeout(_ => resolve('This is async'), 1000) }) } /* Generator Function */ function *gen(){ for(let i = 0; i < 5; i++) { console.log('This is sync') yield asyncFunc().then(console.log) // looks and acts a lot like await } } /* iterate through the generator */ let it = gen() function runGen(){ let p = it.next() if (p.done) return else p.value.then(runGen) } runGen()
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.