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
对象和产生器转换如下:
网友评论