Skip to content

async / await以及和promise、生成器Generator的区别 #21

Open
@TieMuZhen

Description

@TieMuZhen

JS异步发展史

异步最早的解决方案是回调函数,如事件的回调,setInterval/setTimeout中的回调。但是回调函数有一个很常见的问题,就是回调地狱的问题(稍后会举例说明);

为了解决回调地狱的问题,社区提出了Promise解决方案,ES6将其写进了语言标准。Promise解决了回调地狱的问题,但是Promise也存在一些问题,如错误不能被try catch,而且使用Promise的链式调用,其实并没有从根本上解决回调地狱的问题,只是换了一种写法。

ES6中引入Generator函数,Generator是一种异步编程解决方案,Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权,Generator 函数可以看出是异步任务的容器,需要暂停的地方,都用yield语句注明。但是 Generator 使用起来较为复杂。

ES7又提出了新的异步解决方案:async/awaitasyncGenerator函数的语法糖,async/await使得异步代码看起来像同步代码,异步编程发展的目标就是让异步逻辑的代码看起来像同步一样。

Promise常见问题

Q: then块如何中断?
A: then块默认会向下顺序执行,return是不能中断的,可以通过throw来跳转至catch实现中断

Q: Promise是一种将异步转换为同步的方法吗?
A: 完全不是。Promise只不过是一种更良好的编程风格

async / await

async/await 就是 Generator 的语法糖,async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成await。

async

async 函数会返回一个 Promise 对象。如果在函数中 return 一个直接量,async 会把这个直接量通过Promise.resolve()封装成 Promise 对象。所以在最外层不能用 await 获取其返回值的情况下,我们可以用then() 链来处理这个 Promise 对象。更详细请戳async MDN

Promise.resolve(x) 可以看作是 new Promise(resolve => resolve(x)) 的简写,可以用于快速封装字面量对象或其他对象,将其封装成 Promise 实例。

await

async 函数返回一个 Promise 对象,所以 await 可以用于等待一个 async 函数的返回值。但是 await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果,所以,await 后面实际是可以接普通函数调用或者直接量的。所以下面这个示例完全可以正确运行

function getSomething() {
    return "something";
}

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

async function test() {
    const v1 = await getSomething();
    const v2 = await testAsync();
    console.log(v1, v2);
}

test();

如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。

如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。这就是 await 必须用在 async 函数中的原因。async 函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。

async/await 如何处理异步并发问题的?

Promise 之前

/**
 * Promise 之前
 * 
 * 如果使用回调函数实现多个异步并行执行,同一时刻获取最终结果
 * 可以借助 发布订阅/观察者模式
 */

let pubsub = {
    arry: [],
    emit() {
        this.arry.forEach(fn => fn());
    },
    on(fn) {
        this.arry.push(fn);
    }
}

let data = [];
pubsub.on(() => {
    if(data.length === 3) {
        console.log(data);  //[ '22', 'Yvette', 'engineer' ];数组顺序随机
    }
});
fs.readFile('./JS/Async/data/age.txt', 'utf-8', (err, value) => {
    data.push(value);
    pubsub.emit();
});
fs.readFile('./JS/Async/data/name.txt', 'utf-8', (err, value) => {
    data.push(value);
    pubsub.emit();
});
fs.readFile('./JS/Async/data/job.txt', 'utf-8', (err, value) => {
    data.push(value);
    pubsub.emit();
});

使用 Promise

/**
 * 将 fs.readFile 包装成promise接口
 */
function read(url) {
    return new Promise((resolve, reject) => {
        fs.readFile(url, 'utf8', (err, data) => {
            if(err) reject(err);
            resolve(data);
        });
    });
}

/**
 * 使用 Promise
 * 
 * 通过 Promise.all 可以实现多个异步并行执行,同一时刻获取最终结果的问题
 */

Promise.all([
    read('./JS/Async/data/age.txt'),
    read('./JS/Async/data/name.txt'),
    read('./JS/Async/data/job.txt')
]).then(data => {
    console.log(data); //[ '22', 'Yvette', 'engineer' ];数组顺序随机
}).catch(err => console.log(err));

使用 Async/Await

/**
 * 使用 Async/Await
 */

async function readAsync() {
    let data = await Promise.all([
        read('./JS/Async/data/age.txt'),
        read('./JS/Async/data/name.txt'),
        read('./JS/Async/data/job.txt')
    ]);
    return data;
}

readAsync().then(data => {
    console.log(data); //[ '22', 'Yvette', 'engineer' ];数组顺序随机
});

使用 async/await 需要注意什么?

1、await 命令后面的Promise对象,运行结果可能是 rejected,此时等同于 async 函数返回的 Promise 对象被reject。因此需要加上错误处理,可以给每个 await 后的 Promise 增加 catch 方法;也可以将 await 的代码放在try...catch中。
2、多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。

async function f1() {
    await Promise.all([
        new Promise((resolve) => {
            setTimeout(resolve, 600);
        }),
        new Promise((resolve) => {
            setTimeout(resolve, 600);
        })
    ])
}

3、await命令只能用在async函数之中,如果用在普通函数,会报错。
4、async 函数可以保留运行堆栈。

/**
* 函数a内部运行了一个异步任务b()。当b()运行的时候,函数a()不会中断,而是继续执行。
* 等到b()运行结束,可能a()早就运行结束了,b()所在的上下文环境已经消失了。
* 如果b()或c()报错,错误堆栈将不包括a()。
*/
function b() {
    return new Promise((resolve, reject) => {
        setTimeout(resolve, 200)
    });
}
function c() {
    throw Error(10);
}
const a = () => {
    b().then(() => c());
};
a();

报错信息如下,错误堆栈没有包括a()
image

/**
* 改成async函数
*/
const a = async () => {
    await b();
    c();
};
a();

报错信息如下,可以看出 async 函数可以保留运行堆栈。
image

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions