Async/await

2017-12-28  本文已影响0人  一蓑烟雨任平生_cui

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是最最基本的。

上一篇下一篇

猜你喜欢

热点阅读