async function
我们在第四章的“Generators + Promises”中提到过,generatoryield
一个promise给一个类似运行器的工具,它会在promise完成时推进generator —— 有一个提案是要为这种模式提供直接的语法支持。让我们简要看一下这个被提出的特性,它称为async function
。
回想一下第四章中的这个generator的例子:
run( function *main() {
var ret = yield step1();
try {
ret = yield step2( ret );
}
catch (err) {
ret = yield step2Failed( err );
}
ret = yield Promise.all([
step3a( ret ),
step3b( ret ),
step3c( ret )
]);
yield step4( ret );
} )
.then(
function fulfilled(){
// `*main()` 成功地完成了
},
function rejected(reason){
// 噢,什么东西搞错了
}
);
被提案的async function
语法可以无需run(..)
工具就表达相同的流程控制逻辑,因为JS将会自动地知道如何寻找promise来等待和推进。考虑如下代码:
async function main() {
var ret = await step1();
try {
ret = await step2( ret );
}
catch (err) {
ret = await step2Failed( err );
}
ret = await Promise.all( [
step3a( ret ),
step3b( ret ),
step3c( ret )
] );
await step4( ret );
}
main()
.then(
function fulfilled(){
// `main()` 成功地完成了
},
function rejected(reason){
// 噢,什么东西搞错了
}
);
取代function *main() { ..
声明的,是我们使用async function main() { ..
形式声明。而取代yield
一个promise的,是我们await
这个promise。运行main()
函数的调用实际上返回一个我们可以直接监听的promise。这与我们从一个run(main)
调用中拿回一个promise是等价的。
你看到对称性了吗?async function
实质上是 generators + promises + run(..)
模式的语法糖;它们在底层的操作是相同的!
如果你是一个C#开发者而且这种async
/await
看起来很熟悉,那是因为这种特性就是直接由C#的特性启发的。看到语言提供一致性是一件好事!
Babel、Traceur 以及其他转译器已经对当前的async function
状态有了早期支持,所以你已经可以使用它们了。但是,在下一节的“警告”中,我们将看到为什么你也许还不应该上这艘船。
注意: 还有一个async function*
的提案,它应当被称为“异步generator”。你可以在同一段代码中使用yield
和await
两者,甚至是在同一个语句中组合这两个操作:x = await yield y
。“异步generator”提案看起来更具变化 —— 也就是说,它返回一个没有还没有完全被计算好的值。一些人觉得它应当是一个 可监听对象(observable),有些像是一个迭代器和promise的组合。就目前来说,我们不会进一步探讨这个话题,但是会继续关注它的演变。
警告
关于async function
的一个未解的争论点是,因为它仅返回一个promise,所以没有办法从外部 撤销 一个当前正在运行的async function
实例。如果这个异步操作是资源密集型的,而且你想在自己确定不需要它的结果时能立即释放资源,这可能是一个问题。
举例来说:
async function request(url) {
var resp = await (
new Promise( function(resolve,reject){
var xhr = new XMLHttpRequest();
xhr.open( "GET", url );
xhr.onreadystatechange = function(){
if (xhr.readyState == 4) {
if (xhr.status == 200) {
resolve( xhr );
}
else {
reject( xhr.statusText );
}
}
};
xhr.send();
} )
);
return resp.responseText;
}
var pr = request( "http://some.url.1" );
pr.then(
function fulfilled(responseText){
// ajax 成功
},
function rejected(reason){
// 噢,什么东西搞错了
}
);
我构想的request(..)
有点儿像最近被提案要包含进web平台的fetch(..)
工具。我们关心的是,例如,如果你想要用pr
值以某种方法指示撤销一个长时间运行的Ajax请求会怎么样?
Promise是不可撤销的(在本书写作时)。在我和其他许多人看来,它们就不应该是可以被撤销的(参见本系列的 异步与性能)。而且即使一个proimse确实拥有一个cancel()
方法,那么一定意味着调用pr.cancel()
应当真的沿着promise链一路传播一个撤销信号到async function
吗?
对于这个争论的几种可能的解决方案已经浮出水面:
async function
将根本不能被撤销(现状)- 一个“撤销存根”可以在调用时传递给一个异步函数
- 将返回值改变为一个新增的可撤销promsie类型
- 将返回值改变为非promise的其他东西(比如,可监听对象,或带有promise和撤销能力的控制存根)
在本书写作时,async function
返回普通的promise,所以完全改变返回值不太可能。但是现在下定论还是为时过早了。让我们持续关注这个讨论吧。