繁体   English   中英

Javascript异步/等待行为说明

[英]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将暂停整个包含功能的执行。 这会在forwhile循环中暂停。 要仅使用.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语句,则它们将类似地导致函数执行被挂起,直到正在等待的诺言得到解决或拒绝为止。

这种在中途暂停或暂停执行功能的能力的优点在于,它可以在forwhile类的循环结构中for ,并且使得使用这种循环依次执行异步操作比以前编写起来容易得多。 asyncawait (如您所见)。 没有简单的类似的方法仅使用.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())
}

让循环从i0开始。 检查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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM