首頁 > web前端 > js教程 > 主體

JavaScript中的Promise使用詳解_基礎知識

WBOY
發布: 2016-05-16 15:53:09
原創
1796 人瀏覽過

許多的語言,為了將非同步模式處理得更像平常的順序,都包含一種有趣的方案庫,它們被稱之為promises,deferreds,或futures。 JavaScript的promises ,可以促進關注點分離,以取代緊密耦合的介面。 本文講的是基於Promises/A 標準的JavaScript promises。 [http://wiki.commonjs.org/wiki/Promises/A]

Promise的用例:

  •     執行規則
  •     多重遠端驗證
  •     逾時處理
  •     遠端資料請求
  •     動畫
  •     將事件邏輯從應用邏輯解耦
  •     消除回呼函數的恐怖三角形
  •     控制並行的非同步操作

JavaScript promise是一個承諾將在未來傳回值的物件。是具有良好定義的行為的資料對象。 promise有三種可能的狀態:

  1.     Pending(待定)
  2.     Rejected(拒絕)
  3.     Resolved(完成)

一個已經拒絕或完成的承諾屬於已經解決的。一個承諾只能從待定狀態變成已經解決的狀態。之後,承諾的狀態就不變了。承諾可以在它對應的處理完成之後很久還存在。也就是說,我們可以多次取得處理結果。我們透過呼叫promise.then()來取得結果,這個函數一直到承諾對應的處理結束才會回傳。我們可以靈活的串連起一堆承諾。這些串連起來的「then」函數應該回傳一個新的承諾或是最早的那個承諾。
透過這個樣式,我們可以像寫同步程式碼一樣來寫非同步程式碼。主要是透過組合承諾來實現:

  •     堆疊式任務:多處散落在程式碼中的,對應同一個承諾。
  •     並行任務:多重承諾返回同一個承諾。
  •     串列任務:一個承諾,然後接著執行另一個承諾。
  •     上面幾種的組合。

為什麼要這麼麻煩?只用基本的回呼函數不行嗎?

回呼函數的問題

回呼函數適合簡單的重複事件,例如根據點擊來讓一個表單有效,或儲存一個REST呼叫的結果。回呼函數也會使程式碼形成一個鏈,一個回呼函數呼叫一個REST函數,並為REST函數設定一個新的回呼函數,這個新的回呼函數再呼叫另一個REST函數,依此類推。程式碼的橫向增長大於縱向的增長。回呼函數看起來很簡單,直到我們需要一個結果,而且是立刻就要,馬上就用在下一行的計算中。

'use strict';
var i = 0;
function log(data) {console.log('%d %s', ++i, data); };
 
function validate() {
  log("Wait for it ...");
  // Sequence of four Long-running async activities
  setTimeout(function () {
   log('result first');
   setTimeout(function () {
     log('result second');
     setTimeout(function () {
      log('result third');
      setTimeout(function () {
        log('result fourth')
      }, 1000);
     }, 1000);
   }, 1000);
  }, 1000);
 
};
validate();
登入後複製

我使用timeout來模擬非同步操作。管理異常的方法是痛苦的,很容易玩漏下游行為。當我們編寫回調,那麼程式碼組織變得混亂。圖2顯示了一個模擬驗證流可以運行在NodeJS REPL。在下一節,我們將從pyramid-of-doom模式遷移到一個連續的promise。

Figure
 

'use strict';
var i = 0;
function log(data) {console.log('%d %s', ++i, data); };
 
// Asynchronous fn executes a callback result fn
function async(arg, callBack) {
  setTimeout(function(){
   log('result ' + arg);
   callBack();
  }, 1000);
};
 
function validate() {
  log("Wait for it ...");
  // Sequence of four Long-running async activities
  async('first', function () {
   async('second',function () {
     async('third', function () {
      async('fourth', function () {});
     });
   });
  });
};
validate();
登入後複製

在NodeJS REPL執行的結果
 

$ node scripts/examp2b.js
1 Wait for it ...
2 result first
3 result second
4 result third
5 result fourth
$
登入後複製

 

我曾經遇到一個AngularJS動態驗證的情況,根據對應表的值,動態的限製表單項目的值。限制項的有效值範圍被定義在REST服務上。

我寫了一個調度器,根據請求的值,去操作函數棧,以避免回調嵌套。調度器從堆疊中彈出函數並執行。函數的回調會在結束時重新呼叫調度器,直到堆疊被清空。每次回調都記錄所有從遠端驗證呼叫傳回的驗證錯誤。

我認為我寫的玩意兒是一種反模式。如果我用Angular的$http呼叫提供的promise,在整個驗證過程中我的思維會更近似線性形式,就像同步程式設計。平展的promise鍊是可讀的。繼續...
 
使用Promises

其中採用了kew promise函式庫。 Q庫同樣適用。要使用該函式庫,請先使用npm將kew函式庫匯入到NodeJS,然後載入程式碼到NodeJS REPL。

Figure
 

'use strict';
var Q = require('kew');
var i = 0;
 
function log(data) {console.log('%d %s', ++i, data); };
 
// Asynchronous fn returns a promise
function async(arg) {
  var deferred = Q.defer();
  setTimeout(function () {
    deferred.resolve('result ' + arg);\
  }, 1000);
  return deferred.promise;
};
 
// Flattened promise chain
function validate() {
  log("Wait for it ...");
  async('first').then(function(resp){
    log(resp);
    return async('second');
  })
  .then(function(resp){
    log(resp);
    return async('third')
  })
  .then(function(resp){
    log(resp);
    return async('fourth');
  })
  .then(function(resp){
    log(resp);
  }).fail(log);
};
validate();
登入後複製

輸出和使用巢狀回呼時相同:
 

$ node scripts/examp2-pflat.js
1 Wait for it ...
2 result first
3 result second
4 result third
5 result fourth
$
登入後複製

该代码稍微“长高”了,但我认为更易于理解和修改。更易于加上适当的错误处理。在链的末尾调用fail用于捕获链中错误,但我也可以在任何一个then里面提供一个reject的处理函数做相应的处理。

服务器 或 浏览器

Promises在浏览器中就像在NodeJS服务器中一样有效。下面的地址, http://jsfiddle.net/mauget/DnQDx/,指向JSFiddle的一个展示如何使用一个promise的web页面。 JSFiddle所有的代码是可修改的。我故意操作随意动作。你可以试几次得到相反的结果。它是可以直接扩展到多个promise链, 就像前面NodeJS例子。

2015624113608681.jpg (572×265)

并行 Promises

考虑一个异步操作喂养另一个异步操作。让后者包括三个并行异步行为,反过来,喂最后一个行动。只有当所有平行的子请求通过才能通过。这是灵感来自偶遇一打MongoDB操作。有些是合格的并行操作。我实现了promises的流流程图。

2015624113731418.jpg (454×366)

我们怎么会模拟那些在该图中心行的并行promises?关键是,最大的promise库有一个全功能,它产生一个包含一组子promises的父promie。当所有的子promises通过,父promise通过。如果有一个子promise拒绝,父promise拒绝。

让十个并行的promises每个都包含一个文字promise。只有当十个子类通过或如果任何子类拒绝,最后的then方法才能完成。

Figure

var promiseVals = ['To ', 'be, ', 'or ',
  'not ', 'to ', 'be, ', 'that ',
  'is ', 'the ', 'question.'];
 
var startParallelActions = function (){
  var promises = [];
 
  // Make an asynchronous action from each literal
  promiseVals.forEach(function(value){
    promises.push(makeAPromise(value));
  });
 
  // Consolidate all promises into a promise of promises
  return Q.all(promises);
};
 
startParallelActions ().then( . . .
登入後複製

下面的地址, http://jsfiddle.net/mauget/XKCy2/,针对JSFiddle在浏览器中运行十个并行promises,随机的拒绝或通过。这里有完整的代码用于检查和变化if条件。重新运行,直到你得到一个相反的完成。2015624114246516.png (554×370)

孕育 Promise

许多api返回的promise都有一个then函数——他们是thenable。通常我只是通过then处理thenable函数的结果。然而,$q,mpromise,和kew库拥有同样的API用于创建,拒绝,或者通过promise。这里有API文档链接到每个库的引用部分。我通常不需要构造一个promise,除了本文中的包装promise的未知描述和timeout函数。请参考哪些我创建的promises。

Promise库互操作

大多数JavaScript promise库在then级别进行互操作。你可以从一个外部的promise创建一个promise,因为promise可以包装任何类型的值。then可以支持跨库工作。除了then,其他的promise函数则可能不同。如果你需要一个你的库不包含的函数,你可以将一个基于你的库的promise包装到一个新的,基于含有你所需函数的库创建的promise里面。例如,JQuery的promise有时为人所诟病。那么你可以将其包装到Q,$q,mpromise,或者kew库的promise中进行操作。
 
结语

现在我写了这篇文章,而一年前我却是犹豫要不要拥抱promise的那个。我只是单纯地想完成一项工作。 我不想学习新的API,或是打破我原来的代码(因为误解了promise)。我曾经如此错误地认为!当我下了一点注时,就轻易就赢得了可喜的成果。

在这篇文章中,我已经简单给出了一个单一的promise,promise链,和一个并行的promise的promise的的例子。 Promises不难使用。如果我可以使用它们,任何人都可以。 要查看完整的概念,我支持你点击专家写的参考指南。从Promises/A 的参考开始,从事实上的标准JavaScript的Promise 开始。

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板