[译] 深切明白 Promise 五部曲:5. LEGO

原文地点:http://blog.getify.com/promis…

Part4:扩大题目 中,我议论了如何扩大和笼统Promise是何等的罕见,以及这中心的一些题目。然则为何promise关于开发者来讲不是充足友爱的呢?这就是它的设想意图吗?

I’ve Got Friends In Low Places

Promise被设想为初级别的构建块。一个promise就像一个乐高玩具。单个乐高只是一个风趣的玩具。然则假如把它们拼在一起,你会感受到更多的兴趣。

题目是promise不是你小时候玩儿的谁人风趣LEGO,它们不是充溢设想力的打气筒,也不是Gandalf mini-figure(一种乐高玩具)。

都不是,promise只是你的简朴老旧的4X2的砖块。

这并非使它们非常有效。然则它们是你箱子中最主要的组成部份之一。当它们相互离开时它们只是这么个东西,然则当把它们整合在一起它们就会散发出光泽。

换句话说,promise实质上是一个构建在实在用户之上的初级别的API。这是对的:promise并非给开发者运用的,它们是给库作者运用的。

你会从它们那收益许多,然则你极可以不是直接运用它们。你将会运用的是经由许多库组合包装今后的结果。

掌握 VS 值

请许可我改正第一个最大的关于promise的误解:它们不是真正关于流程掌握的

promise固然可以链接在一起来变成近似异步流程掌握的东西。然则末了证实它们并不像你设想的那样善于这个使命。promises确切只是一个值的容器。这个值可以如今就存在也多是将来的一个值。然则不论如何,它只是一个值。这是promise最有意义的优点之一。它们在值的上面建立了一个壮大的笼统使得值不再是暂存的东西。换句话说,不论谁人值如今是不是存在,你都可以用一样的体式格局运用promise。在这个系列的 第三部份 中,我议论过promise必需是不可变的,它们作为值的意义也是基于这个特性的。

promises就像状况的小型的自包括的表现体式格局。它们是可组合的,也就意味着你悉数的递次可以用它们来示意。

限定

就像你不能期望一个零丁的4X2的乐高可以变成一个跑车,让promise成为你的异步流程掌握机制也是一种期望。
那末promises作为一个非暂存的不可变的值关于处置惩罚异步使命意味着什么呢?在它们设想哲学的约束中,有它们善于而且可以有协助的东西。

在剩下的内容中,我会议论这个限定。然则我并不盘算作为一个promise的批判者。我试图去强调扩大和笼统的主要性。

毛病处置惩罚

当我说promise只是一个值的容器的时候我撒了个小慌。实际上,它是一个胜利值或许失利信息的容器。在任何时候,一个promise是一个将来的胜利值或许在猎取这个值时的失利信息。不会凌驾这两种状况。

在某种意义上说,一个promise是一个决议计划构造,一个if..then..else。其他人喜好把它想成一个try..catch构造。不论是哪一种邃晓,你就像在说”要求一个值,不论胜利照样失利”。

就像尤达说,”Do or do not, there is no try”。

斟酌下面这个状况:

function ajax(url) {
    return new Promise( function(resolve,reject){
        // make some ajax request
        // if you get a response, `resolve( answer )`
        // if it fails, `reject( excuses )`
    } );
}

ajax( "http://TheMeaningOfLife.com" )
.then(
    winAtLife,
    keepSearching
);

看到winAtLife()keepSearching()函数了吗?我们在说,”去问问性命的意义,不论你有无找到答案,我们都继承”。

假如我们不传入keepSearching会如何?除了作为一个乐观主义者假定你会找到答案然后在性命长河中取胜,这里会有什么风险呢?

假如promise没有找到性命的意义(或许假如在处置惩罚答案的历程中发作了javascript非常),它会默默地保留着毛病的实际,或许会永久保留着。就算你等上一百万年,你都不会晓得关于答案的要求失利了。

你只能经由过程视察才晓得它失利了。这可以须要深切到形而上学或许量子学的东西。让我们住手在这吧。

所以不带失利处置惩罚函数的promise是一个会默默地失利的promise。这并不好。这意味着假如你忘记了,你会堕入失利的圈套而不是胜利。

所以你会疑心:为何promises会疏忽失利处置惩罚函数呢?由于你可以如今不在乎失利的状况,只需今后某个时候会体贴。我们递次的暂时性意味着体系如今不会晓得你今后会想做什么。如今疏忽失利处置惩罚函数或许对你来讲是正合适的,由于你晓得你会把这个promise链接到另一个promise,而且谁人promise有一个失利处置惩罚函数。

所以promise机制让你可以建立不须要监听失利的promise。

这里有一个很玄妙的题目,极可以也是大多数刚打仗promise的开发者会遇到的题目。

约束我们的链子

为了邃晓这个题目,我们起首须要邃晓promises是如何链接在一起的。我以为你会很快邃晓promise链是壮大而且有一点庞杂的。

ajax( "http://TheMeaningOfLife.com" )
.then(
    winAtLife,
    keepSearching
)
// a second promise returned here that we ignored!
;

ajax(..)挪用产生了第一个promise,然后then(..)挪用产生了第二个promise。我们没有捕捉而且视察在这段代码中的第二个promise,然则我们可以。第二个promise是依据第一个promise处置惩罚函数如何运转来自动变成fulfilled状况(胜利或许失利)。

第二个promise不会在乎第一个promise是胜利照样失利。它在乎第一个promise的处置惩罚函数(不论胜利照样失利)。
这是promise链的症结。然则这有一点不好邃晓,所以反复读上面那段话直到你邃晓为止。

斟酌下promise代码通常是怎样写的(经由过程链):

ajax( ".." )
.then( transformResult )
.then(
    displayAnswer,
    reportError
);

这段代码也可以像下面这么写,结果是一样的:

var promiseA = ajax( ".." );

var promiseB = promiseA.then( transformResult );

var promiseC = promiseB.then(
    displayAnswer,
    reportError
);

// we don't use `promiseC` here, but we could...

Promise A是唯一在乎ajax(..)结果的promise。

Promise B只体贴Promise A在transformResult(..)函数内部是如何处置惩罚的(不是Promise A的结果自身),一样的,Promise C只体贴Promise B在displayAnswer(..)或许reportError(..)函数内部是如何处置惩罚的(不是Promise B结果自身)。

再一次,反复读这段话直到邃晓。

transformResult(..)内部,假如它立时完成了它的使命,然后Promise B就会立时完成,不论胜利照样失利。但是,假如transformResult(..)不能立时完成,而是建立它本身的promise,我们称它为Promise H1(‘H’是’hidden’,由于它是隐蔽在内部的)。底本Promise B返回的守候我们如何处置惩罚Promise A的promise,如今观点上被Promise H1替换了(并非真的替换了,只是被说成一样的)。

所以,如今当你说promiseB.then(..)时,它实际上就像说promiseH1.then(..)。假如Promise H1胜利了,displayAnswer(..)会被挪用,然则假如它失利了,reportError(..)会被挪用。

这就是promise链是如何事变的。

然则,假如Promise A(由ajax挪用返回)失利了会如何?promiseA.then(..)挪用没有注册一个失利处置惩罚函数。它会默默地隐蔽毛病吗?它会的,除了我们链接上Promise B然后在上面注册一个毛病处置惩罚函数:reportError(..)。假如Promise A失利了,transformResult(..)不会被挪用,而且没有毛病处置惩罚函数,所以Promise B立时被标记为失利,所以reportError(..)会被挪用。

假如Promise A胜利了,transformResult(..)会被实行,然后当运转transformResult(..)时有一个毛病会如何?Promise B被标记为失利,然后reportError(..)也会被挪用。

然则这里是风险的处所,这个处所甚至有履历的开发者都邑脱漏的!

假如Promise A胜利了(胜利的ajax(..)),然后Promise B胜利了(胜利的transformResult(..)),然则当运转displayAnswer(..)时有一个毛病会如何?

你或许会以为reportError(..)会被挪用?大多数人会这么想,然则不是的。

为何?由于来自displayAnswer(..)的一个毛病或许失利promise致使一个失利的Promise C。我们监听Promise C失利的状况了吗?细致看看。没有。

为了确保你不会遗漏这类毛病而且让它默默地隐蔽在Promise C状况内部,你也会愿望监听Promise C的失利:

var promiseC = promiseB.then(
    displayAnswer,
    reportError
);

// need to do this:
promiseC.then( null, reportError );

// or this:, which is the same thing:
promiseC.catch( reportError );

// Note: a silently ignored *Promise D* was created here!

OK,所以如今我们捕捉displayAnswer(..)内部的毛病。不能不去记着这个有一点坑爹。

乌龟

然则有一个越发玄妙的题目!假如当处置惩罚displayAnswer(..)返回的毛病时,reportError(..)函数也有一个JS非常会如何?会有人捕捉这个毛病吗?没有。

看!上面有一个隐含的Promise D,而且它会被示知reportError(..)内部的非常。

OMG,你一定会想。什么时候才住手?它会如许一向下去吗?

一些promise库作者以为有必要处置惩罚这个题目经由过程让”平静的毛病”被作为全局非常抛出。然则这类机制该如何得知你不想再链接promise而且供应一个毛病处置惩罚函数呢?它如何晓得什么时候应当转达一个全局非常或许不转达呢?你一定不愿望当你已捕捉而且处置惩罚毛病的状况下依然有许多掌握台毛病信息。

在某种意义上,你须要可以标记一个promise为“final”,就像说“这是我链子中的末了一个promise”或许“我不盘算再链接了,所以这是乌龟住手的处所”。假如在链的末了发作了毛病而且没有被捕捉,然后它须要被报告为一个全局非常。

从表面上我猜想这似乎是很明智的。这类状况下的完成像下面如许:

var promiseC = promiseB.then(
    displayAnswer,
    reportError
);

promiseC
.catch( reportError )
.done(); // marking the end of the chain

你依然须要记着挪用done(),要不然毛病照样会隐蔽在末了一个promsie中。你必需运用稳定的毛病处置惩罚函数。
“恶心”,你一定会这么想。迎接来到promises的欢欣天下。

Value vs Values

关于毛病处置惩罚已说了许多了。另一个中心promsie的限定是一个promise代表一个零丁的值。什么是一个零丁的值呢?它是一个对象或许一个数组或许一个字符串或许一个数字。等等,我还可以在一个容器里放入多个值,就像一个数组或对象中的多个元素。Cool!

一个操纵的终究结果不老是一个值,然则promise并不会如许,这很玄妙而且又是另一个失利圈套:

function ajax(url) {
    return new Promise( function(resolve,reject){
        // make some ajax request
        // if you get a response, `resolve( answer, url )`
        // if it fails, `reject( excuses, url )`
    } );
}

ajax( ".." )
.then(
    function(answer,url){
        console.log( answer, url ); // ..  undefined
    },
    function(excuses,url){
        console.log( excuses, url ); // ..  undefined
    }
);

你看出这里面的题目了吗?假如你不测的尝试通报凌驾一个的值过去,不论传给失利处置惩罚函数照样胜利处置惩罚函数,只需第一个值能被通报过去,其他几个会被默默地丢掉。

为何?我置信这和组合的可展望性有关,或许一些其他花梢的辞汇有关。末了,你不能不记着包裹本身的多个值要不然你就会不知不觉的丧失数据。

并行

实在天下中的app经常在“同一时间”发作凌驾一件事变。实质上说,我们须要构建一个处置惩罚器,并行处置惩罚多个事宜,守候它们悉数完成再实行回调函数。

比拟于promise题目,这是一个异步流程掌握的题目。一个零丁的promise不能表达两个或更多并行发作的异步事宜。你须要一个笼统层来处置惩罚它。

在计算机科学术语中,这个观点叫做一个“门”。一个守候一切使命完成,而且不体贴它们完成递次的门。

在promise天下中,我们增加一个API叫做Promise.all(..),它可以构建一个promise来守候一切通报进来的promise完成。

Promise.all([
    // these will all proceed "in parallel"
    makePromise1(),
    makePromise2(),
    makePromise3()
])
.then( .. );

一个邻近的要领是race()。它的作用和all()一样,除了它只需有一个promise返回音讯就实行回调函数,而不守候其他promise的结果。

当你思索这些要领的时候,你可以会想到许多体式格局来完成这些要领。Promise.all(..)Promise.race(..)是原生供应的,由于这两个要领是很常用到的,然则假如你还须要其他的功用那末你就须要一个库来协助你了。限定的另一个表现就是你很快就会发明你须要本身运用Array的相干要领来治理promise列表,比方.map(..).reduce(..)。假如你对map/reduce不熟习,那末赶忙去熟习一下,由于你会发明当处置惩罚实际天下中promise的时候你经常会须要它们。

荣幸的是,已有许多库来协助你了,而且天天另有许多新的库被制造出来。

Single Shot Of Espresso,Please!

另一个关于promise的事变是它们只会运转一次,然后就不用了。

假如你只须要处置惩罚单个事宜,比方初始化一个也没或许资本加载,那末如许没什么题目。然则假如你有一个反复的事宜(比方用户点击按钮),你每次都须要实行一系列异步操纵会如何呢?Promise并不供应如许的功用,由于它们是不可变的,也就是不能被重置。要反复一样的promise,唯一的要领就是从新定义一个promise。

$("#my_button").click(function(evt){
    doTask1( evt.target )
    .then( doTask2 )
    .then( doTask3 )
    .catch( handleError );
});

太恶心了,不单单议是由于反复建立promise关于效力有影响,而且它关于职责疏散不利。你不能不把多个事宜监听函数放在同一个函数中。假如有一个体式格局来转变这类状况就好了,如许事宜监听和事宜处置惩罚函数就可以离开了。

Microsoft的RxJS库把这类体式格局叫做”视察者形式”。我的asynquence库有一个react(..)要领经由过程简朴的体式格局供应了一个相似的功用。

盲区…

在一个已被运用回调函数的API占有的天下中,把promise插进去到代码中比我们设想的要难题。斟酌下面这段代码:

function myAjax(url) {
    return new Promise( function(resolve,reject){
        ajax( url, function(err,response){
            if (err) {
                reject( err );
            }
            else {
                resolve( response );
            }
        } )
    } );
}

我以为promise处置惩罚了回调地狱的题目,然则它们代码看起来依然像渣滓。我们须要笼统层来使得用promise示意回调变得更简朴。原生的promise并没有供应这个笼统层,所以结果就是经由过程原生promise写出来的代码照样很貌寝。然则假如有笼统层那末事变就变得很简朴了。

比方,我的asynquence库供应了一个errfcb()插件(error-first callback),用它可以构建一个回调来处置惩罚下面这类场景:

function myAjax(url) {
    var sq = ASQ();
    ajax( url, sq.errfcb() );
    return sq;
}

Stop The Presses!

偶然,你想要作废一个promise而去做别的事变,然则假如如今你的promise正处在挂起状况会如何呢?

var pr = ajax( ".." )
.then( transformResult )
.then(
    displayAnswer,
    reportError
);

// Later
pr.cancel(); //  <-- doesn't work!

所以,为了作废promise,你须要引入一下东西:

function transformResult(data) {
    if (!pr.ignored) {
        // do something!
    }
}

var pr = ajax( ".." )
.then( transformResult )
.then(
    displayAnswer,
    reportError
);

// Later
pr.ignored = true; // just hacking around

换句话说,你为了可以作废你的promise,在promise上面加了一层来处置惩罚这类状况。你不能从promise作废注册处置惩罚函数。而且由于一个promise必需不可变,你可以直接作废一个promise这类状况是不许可涌现的。从外部作废一个promise跟转变它的状况没有什么区别。它使得promise变得不牢靠。

许多promise库都供应了这类功用,然则这显著是一个毛病。作废这类行动是不须要promise,然则它可以出如今promise上面的一个笼统层里。

冗杂

另一个关于原生promise的忧郁是有些事变并没有被完成,所以你必需自动手动完成它们,而这些事变关于可扩大性是很主要的,然则这些东西经常会致使令人讨厌的反复代码。

看一个例子,在每个promise的完成步骤中,有一个设定就是你愿望坚持链式构造,所以then(..)要领会返回一个新的promise。然则假如你想要到场一个本身建立的promise而且从一个胜利处置惩罚函数中返回,如许你的promise就可以到场到链的流程掌握中。

function transformResult(data) {
    // we have to manually create and return a promise here
    return new Promise( function(resolve,reject){
        // whatever
    } );
}

var pr = ajax( ".." )
.then( transformResult )
.then(
    displayAnswer,
    reportError
);

差别的是,就像上面诠释的一样,从第一个then(..)返回的隐蔽的promise立时就完成(或许失利),然后你就没办法让剩下的链异步耽误。假如有一个笼统层可以经由过程某种体式格局把自动建立/链接的promise暴露给你,然后你就不须要建立本身的promise来替换了,如许该多好。

换句话说,假如有一个设定假定你须要为了异步的目标运用链,而不是你只是须要美丽得实行异步。(也就是说你确切是愿望你的代码可以异步实行,而不是说愿望全部异步流程看过去好看点)。

另一个例子:你不能直接通报一个已存在的promise给then(..)要领,你必需通报一个返回这个promise的函数。

var pr = doTask2();

doTask1()
.then( pr ); // would be nice, but doesn't work!

// instead:

doTask1()
.then( function(){ return pr; } );

这个限定性是有许多缘由的。然则它只是削弱了有利于坚持可扩大性和可展望性的用法的简约。笼统可以轻易的处置惩罚这个题目。

全剧终

一切这些缘由就是为何原生的promise API是壮大同时也是有局限性的。

关于扩大和笼统是一个成熟的范畴。许多库正在做这些事变。就像我之前说的,asynquence是我本身的promise笼统库。它很小然则很壮大。它处置惩罚了一切博客中提到的promise的题目。

我背面会写一篇细致的博客来引见asynquence是假如处置惩罚这些题目标,所以敬请期待。

深切邃晓Promise五部曲–1.异步题目
深切邃晓Promise五部曲–2.转换题目
深切邃晓Promise五部曲–3.牢靠性题目
深切邃晓Promise五部曲–4.扩大性题目
深切邃晓Promise五部曲–5.乐高题目

末了,安利下我的个人博客,迎接接见:http://bin-playground.top

    原文作者:lakb248
    原文地址: https://segmentfault.com/a/1190000000611040
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞