关于异步Promises

英文原文:What's The Point Of Promises?
  • 迄今为止,可能每一个JavaScript开发者和他们的祖母都据说过Promises。若是你没有,那么你即将会。promises的概念是由CommonJS小组的成员在 Promises/A规范 中提出来的。Promises被逐渐用做一种管理异步操做回调的方法,但出于它们的设计,它们远比那个有用得多。事实上,因为它们的多种用法,有无数人告诉我——在我写过一些关于promises的东西后——我“遗漏了promises的重点”。那么什么是promises的重点呢?

 

一点关于Promises的东西

  • 在我开始promise的“重点”以前,我想我应该给你一点它们如何工做的内貌。一个promise是一个对象——根据Promise/A规范——只须要一个方法:then。then方法带有三个参数:一个成功回调,一个失败回调,和一个前进回调(规范没有要求包括前进回调的实现,可是不少都实现了)。一个全新的promise对象从每一个then的调用中返回。
  • 一个promise能够是三种状态之一:未完成的,完成的,或者失败的。promise以未完成的状态开始,若是成功它将会是完成态,若是失败将会是失败态。当一个promise移动到完成态,全部注册到它的成功回调将被调用,并且会将成功的结果值传给它。另外,任何注册到promise的成功回调,将会在它已经完成之后当即被调用。

  • 一样的事情发生在promise移动到失败态的时候,除了它调用的是失败回调而不是成功回调。对包含前进特性的实现来讲,promise在它离开未完成状态之前的任什么时候刻,均可以更新它的progress。当progress被更新,全部的前进回调(progress callbacks)会被传递以progress的值,并被当即调用。前进回调被以不一样于成功和失败回调的方式处理;若是你在一个progress更新已经发生之后注册了一个前进回调,新的前进回调只会在它被注册之后被已更新的progress调用。
  • 咱们不会进一步深刻promise状态是如何管理的,由于那不在规范以内,并且每一个实现都有差异。在后面的例子中,你将会看到它是如何完成的,但目前这就是全部你须要知道的。

 

处理回调

像前面提到的为异步操做处理回调,是promises的最基本和最普通的用途,让咱们将一个标准的回调与一个采用了promise的回调比较一下。javascript

 

// Normal callback usage
asyncOperation(function() {
    // Here's your callback
});

// Now `asyncOperation` returns a promise
asyncOperation().then(function(){
    // Here's your callback
});

我很怀疑只是看到这个例子的话是否有人会真的关心去使用promises。看起来没有什么好处,除了“then”使得在异步操做完成以后的回调函数被调用这件事看起来更加明显。可是即便是这个好处,咱们如今有了更多的代码(抽象应该使咱们的代码更短,不是吗?)并且promise比标准回调稍微性能差一点。java

可是,不要让这阻碍到你。若是这就是promise能够作的最好的事,这篇文章就不会存在了jquery

 

 

厄运的金字塔

// Normal callback usage => PYRAMID OF DOOOOOOOOM
asyncOperation(function(data){
    // Do some processing with `data`
    anotherAsync(function(data2){
        // Some more processing with `data2`
        yetAnotherAsync(function(){
            // Yay we're finished!
        });
    });
});

// Let's look at using promises
asyncOperation()
.then(function(data){
    // Do some processing with `data`
    return anotherAsync();
})
.then(function(data2){
    // Some more processing with `data2`
    return yetAnotherAsync();
})
.then(function(){
    // Yay we're finished!
});

正如你所见,promises的使用使得事情变扁平并且更可读了。这能起做用是由于——像早先提到的——then返回了一个promise,因此你能够将then的调用不停的串连起来。由then返回的promise装载了由调用返回的值。若是调用返回了一个promise(像这个例子中的情形同样),then返回的 promise装载了与你的回调返回的promise所装载的相同值。这内容不少,所以我将帮助你一步一步的理解它git


异步操做返回一个promise对象。所以咱们在那个promise对象中调用then,而且传给它一个回调函数;then也会返回一个promise。当异步操做结束,它将给promise装上数据。而后(第一次)回调被调用,数据被做为参数传递进去。若是回调不含有返回值,then返回的promise将会当即不带值组装。若是回调返回的不是一个promise,那么then返回的 promise将会当即装载那个数值。若是回调返回一个promise(像例子中的),那么then返回的 promise将等待直到咱们回调返回的promise被彻底装载。一旦咱们回调的 promise被装载,它装载的值(本例中就是data2)将会被提交给then的promise。而后then中的promise装载了data2。等等。听起来有点复杂,但事实上很简单,若是我说的你不能理解,我很是抱歉。我猜我可能不是谈论它的最佳人选。github

 

 

用命名的回调替代

但显然 promises 不是使这个结构扁平化的惟一方法。在写了一篇提到promises解决了厄运的金字塔问题的帖子以后,有我的对该帖评论说……web

我想promises有时是有用的,可是“嵌套”的回调的问题(圣诞树综合症)能够仅用一个命名的函数做为一个参数替代匿名函数的方法日常的处理:

asyncCall( param1, param2, HandlerCallback );

function HandlerCallback(err, res){
// do stuff
}

它的例子只是给出了一层深的例子,但它还是正确的。咱们来扩展我前面的例子,使这个看起来容易些。数据库

 

命名回调

// Normal callback usage => PYRAMID OF DOOOOOOOOM
asyncOperation(handler1);

function handler1(data) {
    // Do some processing with `data`
    anotherAsync(handler2);
}

function handler2(data2) {
    // Some more processing with `data2`
    yetAnotherAsync(handler3);
}

function handler3() {
    // Yay we're finished!
}

看看上面的代码!他们绝对是对的!它就是一个扁平的结构,可是这里有个问题一样也存在于 我之前历来没有注意过的老的回调例子中:依赖性和复用性。依赖性和复用性是相互关联的可逆类型。同样东西依赖的越少,那么它的复用性就越大。在以上的例子中,handler1依赖handler2,handler2依赖handler3.这就意味着handler1不管出于任何目的都不可在被用除非handler2也呈现出来。假如你不打算重用他们,那么给你的函数命名又有什么意义呢?编程

 

最糟糕的的是handler1都不关心在handler2里面发生了什么事情。它压根就不须要handler2除了和它异步工做。所以,让咱们消除这些依赖性而后经过用promise使函数更具复用性。api

 

 

链式回调

asyncOperation().then(handler1).then(handler2).then(handler3);

function handler1(data) {
    // Do some processing with `data`
    return anotherAsync();
}

function handler2(data2) {
    // Some more processing with `data2`
    return yetAnotherAsync();
}

function handler3() {
    // Yay we're finished!
}

这样看起来是否是好多了?假如另外的函数存在的话,如今handler1和handler2都互不相关了。想看看他们是否真的很棒呢?如今handler1能够被用在不须要handler2的状况下了。相反,handler1被操做之后,咱们将能够用另外一个handler。数组

 

 

复用函数

asyncOperation().then(handler1).then(anotherHandler);

function handler1(data) {
    // Do some processing with `data`
    return anotherAsync();
}

function anotherHandler(data2) {
    // Do some really awesome stuff that you've never seen before. It'll impress you
}

如今handler1已经从handler2脱离并且能够被用在了更多的情形中,特别是那些由handler2提供的功能而咱们又不想用的。这就是复用性!评论家解决代码易读性的惟一方法就是经过消除缩进。咱们不想消除缩进仅仅是为了缩进。多层次的缩进仅仅是某些事情错误的标志,问题不必定在它自己。他就像是由脱水引发的头痛。真正的问题是脱水,不是头痛。解决的方法是得到水合物,而不是用一些止痛药。

 

 

并行异步操做

在前面我提到的文章里,我将promises与events在处理异步操做方面作了比较。遗憾的是,按照那些曾提到过的人在评论里给的说法,我比较的不是很成功。我描述出了promises的力量,接着转到events来描述它们的力量,就像在个人特别项目里用到的那样。没有比较和对比。一位评论者写道(修改了一点语法错误):

我想用帖子中的例子是一个坏的对照。有篇论文证实了promises的值将会怎样,若是按下虚构的“启动服务器按钮”,将不只仅是启动一个web服务器,还有一个数据库服务器,当它们都在运行的时候只是更新了UI。

使用promise的.when方法将会使这种“多个异步操做”例子变得普通,然而响应多个异步事件须要一个并不普通的代码量。

他彻底正确。事实上我没有比较那两种状况。那篇文章的要点实际在于说明promises不是异步操做的惟一机制,并且在一些状况下,它们也不必定是最好的。在这个评论者指出的状况下,promises固然是最佳的解决办法。咱们来看看他说的是什么

 


 

jQuery 具备 一个名为when的方法 ,能够带上任意数量的promise参数,并返回一个单一的promise。若是任何一个promise传入失败,when返回的promise也会失败。若是全部的promises被装载,那么每一个值都将会按照promises被定义的顺序传递给附加的回调。

以并行的方式执行无数的异步操做很是有用,而后只要在它们之中的每个结束以后继续执行回调。咱们看一个简单的例子。

 

jQuery.when
// Each of these async functions return a promise
var promise1 = asyncOperation1();
var promise2 = asyncOperation2();
var promise3 = asyncOperation3();

// The $ refers to jQuery
$.when(promise1, promise2, promise3).then(
    function(value1, value2, value3){
        // Do something with each of the returned values
    }
);

人们常常说这是 promises 带来的最好的东西之一,也是 promises 的一部分重要的意义所在。我也认为这是个简化了大量操做的好特性,可是这种 when 方法的机制 根本就没有在任何 Promises 规范中提到,因此我不认为它是 Promises意义所在。有一个规范提到了 when 方法,可是和上面的彻底不一样。就我所知,jQuery 是惟一的实现了这种 when 方法的库。其余的 promises 库,例如  Q, Dojo, 和  when 依照  Promises/B spec 实现了 when 方法, 可是并无实现注释者说起的 when 方法。可是,Q 库有一个   all方法,when.js 也有一个  parallel方法,与上面的 jQuery.when 方法做用同样,只是它们接受一个数组类型的参数,而不是任意数量的参数。

 

 

值的表示

Promise是处理如下场景的更好的方法:

"我想在这个数据库中找一个用户,但find方法是异步的。"

所以,这里咱们有了一个不能马上返回值的find方法。但最终它确实"返回"了一个数值(经过一个回调的方式),而你但愿以某种方式处理那个数值。如今,经过使用一个回调,你能定义一个继续部分,或者说“一些将在之后时间里处理那个数值的代码”

Promise改变了那种“嘿,这里是一些你会发现你用来处理返回数值的代码”。它们是一些容许"find"方法说“嘿,我将忙着找你要找的信息,但与此同时你能继续等着返回结果,并且你能同时以任何你但愿的方式处理它,就像实际的东西!”

Promise表明了真实的数值。那就是陷阱。它们工做在你像处理实际东西同样处理Promise的时候。Promise的JavaScript实现期待你给它传递一个回调函数,这只是一个“巧合”,它不是重要的事情。

我相信这真的就是promise的重点。为何?读一读 Promise/A规范 的第一句“一个promise表明了一个操做的一次完成最终返回的数值。“使它有点明显了,是否是?好吧,即便那就是重点,那也不能阻止我在后面本文中呈现其余人的看法。无论怎么说,咱们再多谈论这个思想一点。

 

结论

    promise的重点是它表明一个操做返回的最终结果值,但使用它们的缘由是使同步操做更好的并行。自从异步编程进入此场景,处处都是弹出的回调,以奇怪的方式遮住咱们的代码。Promise是一种改变其的方法。Promise容许咱们以同步的方式写代码,同时给予咱们代码的异步执行。

相关文章
相关标签/搜索