首頁  >  文章  >  web前端  >  有關Promises非同步問題詳解_javascript技巧

有關Promises非同步問題詳解_javascript技巧

WBOY
WBOY原創
2016-05-16 15:32:281299瀏覽

迄今為止,可能每個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的回調比較一下。

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

我很懷疑只是看到這個例子的話是否有人會真的關心去使用promises。看起來沒有什麼好處,除了「then」使得在非同步操作完成之後的回調函數被呼叫這件事看起來更加明顯。但即使是這個好處,我們現在有了更多的程式碼(抽象應該會使我們的程式碼更短,不是嗎?)而且promise比標準回呼稍微效能差一點。

但是,不要讓這阻礙到你。如果這就是promise可以做的最好的事,這篇文章就不會存在了

厄運的金字塔

// 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所装载的相同值。这内容很多,因此我将帮助你一步一步的理解它

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

用命名的回调替代

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

我想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使函数更具复用性。

链式回调

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允許我們以同步的方式寫入程式碼,同時給予我們程式碼的非同步執行。

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn