JavaScript异步/等待行为说明

时间:2018-08-15 07:27:14

标签: javascript async-await es6-promise

我一直认为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”等。

有人可以解释一下两者之间的区别吗,换句话说,异步/等待在幕后究竟做了什么?

5 个答案:

答案 0 :(得分:2)

您的前两个示例基本上是等效的。但是,你猜怎么着,在这两个示例中不能使用for循环。在await循环中没有简单的编码方式来模仿for语句。这是因为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函数内部等待的诺言解析时,会将一个事件插入事件循环以恢复该函数的执行。当解释器返回到事件循环并到达该事件时,将继续执行该函数,该函数先前在await语句之后的行中停止了。

如果还有其他await条语句正在等待promise,那么它们将类似地导致函数执行被挂起,直到正在等待的promise被解决或拒绝为止。

这种在中途暂停或暂停执行功能的能力的优点在于,它可以在诸如forwhile之类的循环结构中工作,并且可以使用以下命令按顺序执行异步操作一个循环比在您拥有asyncawait之前(您已经发现)更容易编程。仅使用.then()并没有简单的类似方法来编码排序循环。如上所述,有些使用.reduce()。其他人也使用内部函数调用,如上所示。

答案 1 :(得分:1)

异步/等待是下一代的承诺。基本上,await将保留其余代码的执行,直到await解析为止。 例如

async function getUser() {    
     const id = 'zzzz';    
     const user = await get(`http://getuser.xxx/{id}`);

     console.log(user);    
}

执行get函数后唯一的控制台被打印。

答案 2 :(得分:1)

.Select会按照包装盒上的说明进行操作:等待异步函数的响应:

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"); ,然后在5秒钟后将记录"before await",然后记录"This is async"

它基本上使异步功能同步运行。

答案 3 :(得分:0)

孤立地说,是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行。它是一种语法糖,但是在整个功能级别,而不是在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语法进行了大幅度的重新排列,但听起来还是很像我写的内容,不是吗? :)

答案 4 :(得分:0)

async/await可能是语法糖,但不仅仅是then()周围的糖。 generators + promises。生成器时,您会注意到语法几乎与async/await相同,但是使用async/await而不是await。

例如,您可以编写一个看起来与第二个yield循环几乎完全一样的生成器,它将产生相同的行为:

for