美文网首页
Async/await

Async/await

作者: 一蓑烟雨任平生_cui | 来源:发表于2017-12-28 20:30 被阅读0次

    async/await 是一种特殊的语法,能够更好的处理promise,可以让你编写基于Promise的代码像同步一样。

    async

    async 关键字放在函数之前,使得该函数总是返回一个promise对象。如果代码中显式 return 了一个值,那么函数的返回值将会被自动包装成resolved状态下的promise对象。

    例如:

    async function fn() {
      return 1;
    }
    
    fn().then(res => {
        console.log(res) // 1
    })
    
    async函数的使用方式
    // 函数声明式
    async function fn() {  }
    
    // 函数表达式
    const fn = async function () {  }
    
    // ArrowFunc
    const fn = async () => {}
    
    // 对象中定义
    let obj = {
        async fn() {}
    }
    

    当async函数被调用执行的时候,会返回一个Promise的实例。当函数返回值时,promise会执行。如果函数在执行中抛出错误,那么会进入promise的rejected流程。

    比如:

    const foo = async () => { 
        throw new Error('err');
    }
    
    foo()
        .then(res => {
            console.log(res);
        })
        .catch(err=> {
            console.log(err) // Error: err
        })
    

    await

    await关键字只能在async函数中使用。可以用来等待Promise状态变成resolved并有返回值。await后面通常跟的是一个promise对象,如果不是,会立即被包装成resoled状态的promise。

    async function foo() {
        let result = await 'ok'
        console.log(result); // ok
    }
    foo();
    
    1. 当用变量接收await的返回值时,值为promise为resolved状态下的值。
    const foo = async () => {
        let result = await Promise.resolve('ok');
        console.log(result); // ok
    }
    foo();
    
    1. 函数执行中,遇到await,会首先返回promise,状态为pending,并且下面的代码会停止执行,待awiat后面的Promise执行完毕后才继续执行下面的代码,直到函数体中的代码全部执行完毕后,函数返回的promise状态才变成resolved。
    function bar() {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('ok');
            }, 1000);
        })
    }
    
    async function foo() {
        let result = await bar();
        console.log(result); // ok
        console.log('last'); 
    }
    
    console.log(foo());
     
    

    输出顺序:

    Markdown

    首先打印出pending状态的promise对象。

    等到await后面的异步函数执行完后,才依次打印出ok、last,同时Promise的状态成为resolved。

    Markdown

    由此可以看出,当在async函数中运行,当遇到await关键字时,会等待关键字后面的函数执行完毕才运行后面的代码。当所有的执行完后函数返回的Promise对象的状态才变成resolved。

    异常处理

    我们知道在promise中是通过catch来捕获异常的。但是在async中则使用try/catch来捕获异常。

    1. 如果await后面的 promise 正常resolve,await promise便会返回结果。但是在reject的情况下,便会抛出异常,并且这种异常需要用try/catch来捕获,否则会导致进程崩溃。
    const baz = () => {
        return Promise.reject('Oops');
    }
    const foo = async () => { 
        try {
            await baz();
        } catch(e) {
            // e 为 baz()返回的promise对象中 reject出来的值
            console.log(e); // Oops
        }
    }
    foo();
    
    1. 如果try中有多个await,其中一个await后面的promise为reject,那么在catch中会抛出第一个异常。
      如下:
    // 异步操作,返回promise对象
    const bar = () => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                reject('Oops bar error');
            }, 1000)
        })
    }
    
    const baz = () => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                reject('Oops baz error');
            }, 1000)
        })
    }
        
    const foo = async () => { 
        try {
            await bar();
            await baz();
        } catch(e) {
            // e 为 bar()返回的promise对象中 reject出来的值
            console.log(e); // Oops bar error
        }
    }
    
    foo();
    

    由此可以如果有多个 await 后面的promise都为reject状态时,只能捕获第一个异常。

    1. 以下这种方式都会捕获到。由于是同步的效果,所以第二次捕获的异常是第一次捕获1s后。
    const fn = async () => {
        try {
            await bar();
        } catch (e) {
            console.log(e) // Oops bar error
        }
    
        try {
            await baz();
        } catch (e) {
            console.log(e) // Oops baz error
        }
    }
    
    fn();
    
    // 第二次和第一次的顺序相隔1s
     
    
    1. 如果await后面的promise中抛出异常,那么等同于async函数返回的 Promise 对象被reject。如下:
    async function foo() {
      await new Promise((resolve, reject) => {
        throw new Error('Oops');
      });
    }
    
    foo()
        .then(res => console.log('ok', res))
        .catch(err => console.log('faile', err)) // faile Error: Oops
    
    
    1. try里边的promise中的异常不会在catch中捕获,因为异常发生在promise中,只能通过promise的catch()捕获。
    const foo = () => {
        return new Promise((resolve, reject) => {
            resolve('ok');
        })
    }
    
    const bar = () => {
        try {
            foo()
                .then(res => {
                    let data = JSON.parse(res);
                }).catch(err => {
                    console.log(err); //  SyntaxError: Unexpected token o in JSON at position 0
                })
        } catch(e) {
            // 这里不会捕获到promise中的异常
            console.log(e);
        }
    }
    
    bar();
    

    并行和串行

    1. 串行
      如果有多个await,那么会按照顺序一个个的执行。
    const p1 = async () => {
        return new Promise(resolve => {
            setTimeout(() => {
                resolve({ name: 'Rose' })
            }, 1000);
        })
    }
    
    const p2 = async () => {
        return new Promise(resolve => {
            setTimeout(() => {
                resolve({ name: 'Rose' })
            }, 1000);
        })
    }
    
    const getInfo = async () => {
        console.time('total time');
        let p1Info = await p1();
        let p2Info = await p2();
        console.timeEnd('total time'); // total time: 2006.705810546875ms
    } 
    
    getInfo()
    

    因为每个await后面的异步函数都会延时1s,所以总耗时大于2s。

    1. 并行

    如果有多个await,那么先将await后面的异步函数的返回值保存变量中。

    const getInfo = async() => {
        console.time('total time');
        const [p1Info, p2Info] = await Promise.all([
            p1(),
            p2()
        ])
        console.timeEnd('total time'); // total time: 1003.5810546875ms
    }
    getInfo()
    

    Promise.all()返回值是一个新的promise对象,值为数组。

    1. 循环中的串行
    const p = async (name) => {
        return new Promise(resolve => {
            let user = {name};
            setTimeout(() => {
                resolve(user)
            }, 1000)
        })
    }
    let arr = ['bob', 'mike'];
    const getInfo = async() => {
        console.time('total time');
    
        for (let name of arr) {
            const p3Info = await p(name);
            // 每隔1s输出一次
            console.log(p3Info.name);
        }
        console.timeEnd('total time'); // total time: 2007.77783203125ms
    }
    

    输出顺序:
    bob
    mike
    total time: 2007.77783203125ms

    1. 循环中串行改并行
    const getInfo = async() => {
        console.time('total time');
    
        // 将所有的promise对象保存在数组中
        let promises = arr.map(name => {
            return p3(name);
        })
        
        for(let promise of promises) {
            const Info = await promise;
            console.log(Info.name);
        }
        console.timeEnd('total time'); // total time: 1006.291015625ms
    }
    

    总结:

    async函数之前的关键字有两个作用:

    1. 使它总是返回一个promise。
    2. 允许在其中使用await。

    await promise之前的关键字使得JavaScript等待,直到这个promise的状态为resolved

    1. 如果是reject,则产生异常,需通过try/catch捕获。
    2. 否则,它返回结果,所以我们可以将它的值赋值给一个变量。

    async/await使我们少写promise.then/catch,但是不要忘记它们是基于promise的。

    此外,网上很多关于promise的文章都会提到ajax的回调地狱,以此来说明promise的诞生只是用来解决异步的,其实不然。promise 只是解决异步的一种方式。

    如果使用async/await,不仅代码简介,甚至有同步的feel。

    promise是一种语法、一种形式,目前很多东西都是基于promise实现的,比如:jquery中的ajax,fetch,以及vue react中的很多功能也都使用了promise。所以promise是最最基本的。

    相关文章

      网友评论

          本文标题:Async/await

          本文链接:https://www.haomeiwen.com/subject/xltbgxtx.html