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”。你可以在同一段代码中使用yieldawait两者,甚至是在同一个语句中组合这两个操作: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,所以完全改变返回值不太可能。但是现在下定论还是为时过早了。让我们持续关注这个讨论吧。

results matching ""

    No results matching ""