Javascript 开发:用「async / await」语法

2021-11-30  本文已影响0人  you的日常

Promise虽然可以将异步程序用看起来像同步程序的方式来撰写,但写法还是与实际的同步程序有不小的差异。也因为Promise有这样的问题,所以后来的 ES2017 引入了「async / await」语法,可以完全地以同步程序的写法使用异步程序。

「async / await」是Promise和产生器的语法糖

在前面的章节中,我们有看过以下的程序:

 async function infiniteLoop() {
     while (1) {
    }
 }

 infiniteLoop();
 console.log("Hello!");

「async / await」语法的功能是用 ES6 添加的Promise和产生器来实现的。加上 async 关键字的函数,会变成异步函数,其主体会变成产生器函数的主体(不过我们无法直接在异步函数中使用yield关键字),而异步函数的主体中会再创建Promise对象实体来将产生器函数产生出来的迭代器的迭代结果当作「正确值」回传出来(用resolve函数)。异步函数则会将这个Promise对象回传出来。

如下:

function infiniteLoop() {
     const generator = function* () {
         while (1) {
         }
     };

     return new Promise<void>(function (resolve) {
         const iterator = generator();
         const result = iterator.next();
         resolve(result.value);
   });
 }

 infiniteLoop();
console.log("Hello!");

以上程序,会在第 10 行陷入无穷循环。注意,Hello!文本并不会被输出,因为传入Promise对象建构子的回呼函数是会被同步运行的,换句话说它不会等到 JavaScript 线程进入事件循环后才被调用。

通过上面的转换说明,我们可以知道原先这个infiniteLoop异步函数的回传值类型为Promise<void>。所以若要明确地写出infiniteLoop异步函数的回传值类型,就会变成以下这样:

async function infiniteLoop(): Promise<void> {
    while (1) {
    }
}

infiniteLoop();
console.log("Hello!");

我们试着让异步函数回传随便的值出来看看,程序如下:

async function f(): Promise<number> {
    return 123;
 }

 f().then((value) => {
         console.log(value);
     })
     .catch((err) => {
        console.error(err);
   });

console.log("Hello!");

以上程序的输出结果为:

> Hello!
> 123

传入Promise对象实体的then方法的回呼函数需要通过微任务来调用,所以它在 JavaScript 线程进入事件循环后才会被运行,因此Hello!文本会在123之前输出。

以上程序,用Promise对象和产生器转换如下:

function f() {
    const generator = function* () {
        return 123;
    };

    return new Promise<number>(function (resolve) {
        const iterator = generator();

        const result = iterator.next();

        resolve(result.value);
    });
}

f()
    .then((value) => {
        console.log(value);
    })
    .catch((err) => {
        console.error(err);
    });

console.log("Hello!");

您可以能会有这样的疑问:为什么要用到产生器?这是因为await关键字要依靠产生器的yield关键字功能来实作。

await关键字可以用来「等待」一个Promise对象实体结束运行,并将「正确值」直接回传;将「错误值」直接用throw关键字抛出。例如:

async function f(): Promise<number> {
    return 123;
}

try {
    const value = await f();

    console.log(value);
} catch (err) {
    console.error(err);
}

console.log("Hello!");

当然,以上程序是无法编译的,因为我们无法在产生器函数之外使用yield关键字(await关键字需要被转换为yield关键字),所以await关键字必须要用在异步函数之中。

为了让以上程序能够成功编译,可以将其改写如下:

async function f(): Promise<number> {
    return 123;
}

async function main(): Promise<void> {
    try {
        const value = await f();

        console.log(value);
    } catch (err) {
        console.error(err);
    }

    console.log("Hello!");
}

main();

以上程序的输出结果为:

> 123
> Hello!

以上程序,用Promise对象和产生器转换如下:

上一篇下一篇

猜你喜欢

热点阅读