一次性让你懂async/await,解决回调地狱

什么是async?

async 函数是 Generator 函数的语法糖。使用 关键字 async 来表示,在函数内部使用 await 来表示异步。相较于 Generatorasync 函数的改进在于下面四点:

  • 内置执行器。Generator 函数的执行必须依靠执行器,而 async 函数自带执行器,调用方式跟普通函数的调用一样

  • 更好的语义。asyncawait 相较于 *yield 更加语义化
  • 更广的适用性。co 模块约定,yield 命令后面只能是 Thunk 函数或 Promise对象。而 async 函数的 await 命令后面则可以是 Promise 或者 原始类型的值(Numberstringboolean,但这时等同于同步操作)
  • 返回值是 Promiseasync 函数返回值是 Promise 对象,比 Generator 函数返回的 Iterator 对象方便,可以直接使用 then() 方法进行调用


  • 此处总结参考自:理解async/await

    asyncES7新出的特性,表明当前函数是异步函数,不会阻塞线程导致后续代码停止运行。

    怎么用
    申明之后就可以进行调用了

    async function asyncFn() {
      return 'hello world';
    }
    asyncFn();

    这样就表示这是异步函数,返回的结果

    返回的是一个promise对象,状态为resolved,参数是return的值。那再看下面这个函数

    async function asyncFn() {
      return '我后执行';
    }
    asyncFn().then(success => console.log(success));
    console.log('我先执行');

    //打印结果

    我先执行
    我后执行

    上面的执行结果是先打印出’我先执行’,虽然是上面async()先执行,但是已经被定义异步函数了,不会影响后续函数的执行。可以理解为console.log优先于async

    现在理解了async()基本的使用,那还有什么特性呢?

    async定义的函数内部会默认返回一个promise对象,如果函数内部抛出异常或者是返回reject,都会使函数的promise状态为失败reject

    async function e() {    
    throw new Error('has Error');
    }
    e().then(success => console.log('成功', success))
    .catch(error => console.log('失败', error));

    我们看到函数内部抛出了一个异常,返回rejectasync函数接收到之后,判定执行失败进入catch,该返回的错误打印了出来。

    async function throwStatus() {    
    return '可以返回所有类型的值'
    }
    throwStatus().then(success => console.log('成功', success))
    .catch(error => console.log('失败', error));

    //打印结果

    成功 可以返回所有类型的值

    async函数接收到返回的值,发现不是异常或者reject,则判定成功,这里可以return各种数据类型的值,false,NaN,undefined…总之,都是resolve

    但是返回如下结果会使async函数判定失败reject

    1.内部含有直接使用并且未声明的变量或者函数。
    2.内部抛出一个错误throw new Error或者返回reject状态return Promise.reject(‘执行失败’)
    3.函数方法执行出错(🌰:Object使用push())等等…

    还有一点,在async里,必须要将结果return回来,不然的话不管是执行reject还是resolved的值都为undefine,建议使用箭头函数。

    其余返回结果都是判定resolved成功执行。

    //正确reject方法。必须将reject状态return出去。
    async function PromiseError() {
    return Promise.reject('has Promise Error');
    }

    //这是错误的做法,并且判定resolve,返回值为undefined,并且Uncaught报错
    async function PromiseError() {
    Promise.reject('这是错误的做法');
    }

    PromiseError().then(success => console.log('成功', success))
    .catch(error => console.log('失败', error));

    我们看到第二行多了个Promise对象打印,不用在意,这个是在Chrome控制台的默认行为,我们平常在控制台进行赋值也是同样的效果。如果最后执行语句或者表达式没有return返回值,默认undefined,做个小实验。

    var a = 1;
    //undefined
    ------------------------------------------------------------
    console.log(a);
    //1
    //undefined
    ------------------------------------------------------------
    function a(){ console.log(1) }
    a();
    //1
    //undefined
    ------------------------------------------------------------
    function b(){ return console.log(1) }
    b();
    //1
    //undefined
    ------------------------------------------------------------
    function c(){ return 1}
    c();
    //1
    ------------------------------------------------------------
    async function d(){
    '这个值接收不到'
    }
    d().then(success => console.log('成功',success));
    //成功 undefined
    //Promise { <resolved>: undefined }
    -----------------------------------------------------------
    async function e(){
    return '接收到了'
    }
    e().then(success => console.log('成功',success));
    //成功 接收到了
    //Promise { <resolved>: undefined }

    最后一行Promise { : undefined } 是因为返回的是console.log执行语句,没有返回值。

    d().then(success => console.log('成功',success)}
    等同于
    d().then(function(success){
    return console.log('成功',success);
    });

    识完了async,来讲讲await

    await是什么?

    await意思是async wait(异步等待)。这个关键字只能在使用async定义的函数里面使用。任何async函数都会默认返回promise,并且这个promise解析的值都将会是这个函数的返回值,而async函数必须等到内部所有的 await 命令的 Promise 对象执行完,才会发生状态改变。

    打个比方,await是学生,async是校车,必须等人齐了再开车。

    就是说,必须等所有await 函数执行完毕后,才会告诉promise我成功了还是失败了,执行then或者catch

    async function awaitReturn() {     
    return await 1
    };
    awaitReturn().then(success => console.log('成功', success))
    .catch(error => console.log('失败',error))

    在这个函数里,有一个await函数,async会等到await 1 这一步执行完了才会返回promise状态,毫无疑问,判定resolved

    很多人以为await会一直等待之后的表达式执行完之后才会继续执行后面的代码,实际上await是一个让出线程的标志。await后面的函数会先执行一遍,然后就会跳出整个async函数来执行后面js栈的代码。等本轮事件循环执行完了之后又会跳回到async函数中等待await后面表达式的返回值,如果返回值为非promise则继续执行async函数后面的代码,否则将返回的promise放入Promise队列(PromiseJob Queue

    来看个简单点的例子

    const timeoutFn = function(timeout){ 
    return new Promise(function(resolve){
    return setTimeout(resolve, timeout);
    });
    }

    async function fn(){
    await timeoutFn(1000);
    await timeoutFn(2000);
    return '完成';
    }

    fn().then(success => console.log(success));

    这里本可以用箭头函数写方便点,但是为了便于阅读本质,还是换成了ES5写法,上面执行函数内所有的await函数才会返回状态,结果是执行完毕3秒后才会弹出’完成’。

    正常情况下,await 命令后面跟着的是 Promise ,如果不是的话,也会被转换成一个 立即 resolvePromise

    也可以这么写

    function timeout(time){
    return new Promise(function(resolve){
    return setTimeout(function(){
    return resolve(time + 200)
    },time);
    })
    }

    function first(time){
    console.log('第一次延迟了' + time );
    return timeout(time);
    }
    function second(time){
    console.log('第二次延迟了' + time );
    return timeout(time);
    }
    function third(time){
    console.log('第三次延迟了' + time );
    return timeout(time);
    }

    function start(){
    console.log('START');
    const time1 = 500;
    first(time1).then(time2 => second(time2) )
    .then(time3 => third(time3) )
    .then(res => {
    console.log('最后一次延迟' + res );
    console.timeEnd('END');
    })
    };
    start();

    这样用then链式回调的方式执行resolve

    //打印结果

    START
    第一次延迟了500
    第二次延迟了700
    第三次延迟了900
    最后一次延迟1100
    END

    用async/await呢?

    async function start() {
    console.log('START');
    const time1 = 500;
    const time2 = await first(time1);
    const time3 = await second(time2);
    const res = await third(time3);
    console.log(`最后一次延迟${res}`);
    console.log('END');
    }
    <code class="codes">start</code>();
    达到了相同的效果。但是这样遇到一个问题,如果<code class="codes">await</code>执行遇到报错呢

    async function start() {
    console.log('START');
    const time1 = 500;
    const time2 = await first(time1);
    const time3 = await Promise.reject(time2);
    const res = await third(time3);
    console.log(`最后一次延迟${res}`);
    console.log('END');
    }
    start();

    返回reject后,后面的代码都没有执行了,以此迁出一个例子:

    let last;
    async function throwError() {
    await Promise.reject('error');
    last = await '没有执行';
    }
    throwError().then(success => console.log('成功', last))
    .catch(error => console.log('失败',last))

    其实async函数不难,难在错处理上。
    上面函数,执行的到await排除一个错误后,就停止往下执行,导致last没有赋值报错。

    async里如果有多个await函数的时候,如果其中任一一个抛出异常或者报错了,都会导致函数停止执行,直接reject;
    怎么处理呢,可以用try/catch,遇到函数的时候,可以将错误抛出,并且继续往下执行。

    let last;
    async function throwError() {
    try{
    await Promise.reject('error');
    last = await '没有执行';
    }catch(error){
    console.log('has Error stop');
    }
    }
    throwError().then(success => console.log('成功', last))
    .catch(error => console.log('失败',last))

    这样的话,就可以继续往下执行了。

    来个🌰练习下

    function testSometing() {
    console.log("testSomething");
    return "return testSomething";
    }

    async function testAsync() {
    console.log("testAsync");
    return Promise.resolve("hello async");
    }

    async function test() {
    console.log("test start...");

    const testFn1 = await testSometing();
    console.log(testFn1);

    const testFn2 = await testAsync();
    console.log(testFn2);

    console.log('test end...');
    }

    test();

    var promiseFn = new Promise((resolve)=> {
    console.log("promise START...");
    resolve("promise RESOLVE");
    });
    promiseFn.then((val)=> console.log(val));

    console.log("===END===")

    执行结果

    //执行结果

    test start...
    testSomething
    promise START...
    ===END===
    return testSomething
    testAsync
    promise RESOLVE
    hello async
    test end...

    我们一步步来解析

    首先test()打印出test start

    然后 testFn1 = await testSomething(); 的时候,会先执行testSometing()这个函数打印出“testSometing”的字符串。

    之后因为await会让出线程就会去执行后面的。testAsync()执行完毕返回resolve,触发promiseFn打印出“promise START…”。

    接下来会把返回的Promiseresolve(“promise RESOLVE“)放入Promise队列(PromiseJob Queue),继续执行打印“===END===”。

    等本轮事件循环执行结束后,又会跳回到async函数中(test()函数),等待之前await 后面表达式的返回值,因为testSometing() 不是async函数,所以返回的是一个字符串“returntestSometing”。

    test()函数继续执行,执行到testFn2(),再次跳出test()函数,打印出“testAsync”,此时事件循环就到了Promise的队列,执行promiseFn.then( val=>console.log(val));打印出“promise RESOLVE”。

    之后和前面一样 又跳回到test函数继续执行console.log(testFn2)的返回值,打印出“hello async”。

    最后打印“test end…”。

    加点料,让testSomething()变成async

    async function testSometing() {
    console.log("testSomething");
    return "return testSomething";
    }

    async function testAsync() {
    console.log("testAsync");
    return Promise.resolve("hello async");
    }

    async function test() {
    console.log("test start...");

    const testFn1 = await testSometing();
    console.log(testFn1);

    const testFn2 = await testAsync();
    console.log(testFn2);

    console.log('test end...');
    }

    test();

    var promiseFn = new Promise((resolve)=> {
    console.log("promise START...");
    resolve("promise RESOLVE");
    });
    promiseFn.then((val)=> console.log(val));

    console.log("===END===")

    执行结果

    //执行结果

    test start...
    testSomething
    promise START...
    ===END===
    promise RESOLVE
    return testSomething
    testAsync
    hello async
    test end...

    和上一个例子比较发现promiseFn.then((val)=> console.log(val));先于console.log(testFn1);执行,原因是因为现在testSomething()已经是async函数,返回的是一个Promise对象要要等它resolve,所以将当前Promise推入队列,所以会继续跳出test()函数执行后续代码。之后就开始执行Promise的任务队列了,所以先执行了promise.then((val)=> console.log(val));因为这个Promise对象先推入队列。

    越来越多的人正在研究据说是异步终极编程解决方案的async/await,但是大部分人对这个方法内部怎么执行的还不是很了解,整理了await之后js的执行顺序,希望对你们有所帮助

    是一种编写异步代码的新方法。之前异步代码的方案是callbackpromise
    建立在 promise 的基础上,与promise一样也是非阻塞的。
    async/await 让异步代码看起来、表现起来更像同步代码。这正是其威力所在。
    参考文献:理解 JavaScriptasync/await

    本文标题:一次性让你懂async/await,解决回调地狱

    文章作者:Seven

    发布时间:2018年06月17日 - 00:06

    最后更新:2018年06月20日 - 16:06

    原始链接:http://www.yuanziwen.cn/2018/06/17/一次性让你动async-await,解决回调地狱/

    许可协议: 署名-非商业性使用 转载请保留原文链接及作者。