DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> 關於JavaScript >> 理解JavaScript中Promise的使用
理解JavaScript中Promise的使用
編輯:關於JavaScript     

Javascript 采用回調函數(callback)來處理異步編程。從同步編程到異步回調編程有一個適應的過程,但是如果出現多層回調嵌套,也就是我們常說的厄運的回調金字塔(Pyramid of Doom),絕對是一種糟糕的編程體驗。於是便有了 CommonJS 的 Promises/A 規范,用於解決回調金字塔問題。本文先介紹 Promises 相關規范,然後再通過解讀一個迷你的 Promises 以加深理解。

什麼是 Promise
一個 Promise 對象代表一個目前還不可用,但是在未來的某個時間點可以被解析的值。它允許你以一種同步的方式編寫異步代碼。例如,如果你想要使用 Promise API 異步調用一個遠程的服務器,你需要創建一個代表數據將會在未來由 Web 服務返回的 Promise 對象。唯一的問題是目前數據還不可用。當請求完成並從服務器返回時數據將變為可用數據。在此期間,Promise 對象將扮演一個真實數據的代理角色。接下來,你可以在 Promise 對象上綁定一個回調函數,一旦真實數據變得可用這個回調函數將會被調用。

Promise 對象曾經以多種形式存在於許多語言中。

去除厄運的回調金字塔(Pyramid of Doom)
Javascript 中最常見的反模式做法是回調內部再嵌套回調。

// 回調金字塔
asyncOperation(function(data){
 // 處理 `data`
 anotherAsync(function(data2){
   // 處理 `data2`
   yetAnotherAsync(function(){
     // 完成
   });
 });
});

引入 Promises 之後的代碼

promiseSomething()
.then(function(data){
  // 處理 `data`
  return anotherAsync();
})
.then(function(data2){
  // 處理 `data2`
  return yetAnotherAsync();
})
.then(function(){
  // 完成
});

Promises 將嵌套的 callback,改造成一系列的.then的連綴調用,去除了層層縮進的糟糕代碼風格。Promises 不是一種解決具體問題的算法,而已一種更好的代碼組織模式。接受新的組織模式同時,也逐漸以全新的視角來理解異步調用。

各個語言平台都有相應的 Promise 實現

  • Java's java.util.concurrent.Future
  • Python's Twisted deferreds and PEP-3148 futures
  • F#'s Async
  • .Net's Task
  • C++ 11's std::future
  • Dart's Future
  • Javascript's Promises/A/B/D/A+

下面我來相信了解一下 javascript 語言環境下各個規范的一些細節。

Promises/A 規范
promise 表示一個最終值,該值由一個操作完成時返回。

  • promise 有三種狀態:**未完成** (unfulfilled),**完成** (fulfilled) 和**失敗** (failed)。
  • promise 的狀態只能由**未完成**轉換成完成,或者**未完成**轉換成**失敗** 。
  • promise 的狀態轉換只發生一次。

promise 有一個 then 方法,then 方法可以接受 3 個函數作為參數。前兩個函數對應 promise 的兩種狀態 fulfilled 和 rejected 的回調函數。第三個函數用於處理進度信息(對進度回調的支持是可選的)。

promiseSomething().then(function(fulfilled){
    //當promise狀態變成fulfilled時,調用此函數
  },function(rejected){
    //當promise狀態變成rejected時,調用此函數
  },function(progress){
    //當返回進度信息時,調用此函數
  });

如果 promise 支持如下連個附加方法,稱之為可交互的 promise

  • get(propertyName)

獲得當前 promise 最終值上的一個屬性,返回值是一個新的 promise。

  • call(functionName, arg1, arg2, ...)

調用當然 promise 最終值上的一個方法,返回值也是一個新的promise。

Promises/B 規范
在 Promises/A 的基礎上,Promises/B 定義了一組 promise 模塊需要實現的 API

when(value, callback, errback_opt)
如果 value 不是一個 promise ,那麼下一事件循環callback會被調用,value 作為 callback 的傳入值。如果 value 是一個 promise,promise 的狀態已經完成或者變成完成時,那麼下一事件循環 callback 會被調用,resolve 的值會被傳入 callback;promise 的狀態已經失敗或者變成失敗時,那麼下一事件循環 errback 會被調用,reason 會作為失敗的理由傳入 errback。

asap(value, callback, errback_opt)
與 when 最大的區別,如果 value 不是一個 promise,會被立即執行,不會等到下一事件循環。

enqueue(task Function)
盡可能快地在接下來的事件循環調用 task 方法。

get(object, name)
返回一個獲得對象屬性的 promise。

post(object, name, args)
返回一個調用對象方法的 promise。

put(object, name, value)
返回一個修改對象屬性的 promise。

del(object, name)
返回一個刪除對象屬性的 promise。

makePromise(descriptor Object, fallback Function)
返回一個 promise 對象,該對象必須是一個可調用的函數,也可能是可被實例化的構造函數。

  • 第一個參數接受一個描述對象,該對象結構如下,
{ "when": function(errback){...}, "get": function(name){...}, "put": function(name, value){...}, "post": function(name, args){...}, "del": function(name){...}, } 

上面每一個注冊的 handle 都返回一個 resolved value或者 promise。

  • 第二個參數接受一個 fallback(message,...args) 函數,當沒有 promise 對象沒有找到對應的 handle 時該函數會被觸發,返回一個 resolved value 或者 promise。

defer()
返回一個對象,該對象包含一個 resolve(value) 方法和一個 promise 屬性。
當 resolve(value) 方法被第一次調用時,promise 屬性的狀態變成 完成,所有之前或之後觀察該 promise 的 promise 的狀態都被轉變成 完成。value 參數如果不是一個 promise ,會被包裝成一個 promise 的 ref。resolve 方法會忽略之後的所有調用。

reject(reason String)
返回一個被標記為 失敗 的 promise。
一個失敗的 promise 上被調用 when(message) 方法時,會采用如下兩種方法之一
1. 如果存在 errback,errback 會以 reason 作為參數被調用。when方法會將 errback 的返回值返回。
2. 如果不存在 errback,when 方法返回一個新的 reject 狀態的promise 對象,以同一 reason 作為參數。

ref(value)
如果 value 是 promise 對象,返回 value 本身。否則,返回一個resolved 的 promise,攜帶如下 handle。
1. when(errback),忽略 errback,返回 resolved 值
2. get(name),返回 resolved 值的對應屬性。
3. put(name, value) ,設置 resolved 值的對應屬性。
4. del(name),刪除 resolved 值的對應屬性。
5. post(name, args), 調用 resolved 值的對應方法。
6. 其他所有的調用都返回一個 reject,並攜帶 "Promise does not handle NAME" 的理由。

isPromise(value) Boolean
判斷一個對象是否是 promise

method(name String)
獲得一個返回 name 對應方法的 promise。返回值是 "get", "put", "del" 和 "post" 對應的方法,但是會在下一事件循環返回。

Promises/D 規范
為了增加不同 promise 實現之間的可互操作性,Promises/D 規范對promise 對象和 Promises/B 規范做了進一步的約定。以達到鴨子類型的效果(Duck-type Promise)。

簡單來說Promises/D 規范,做了兩件事情,

1、如何判斷一個對象是 Promise 類型。
2、對 Promises/B 規范進行細節補充。
甄別一個 Promise 對象
Promise 對象必須是實現 promiseSend 方法。
1. 在 promise 庫上下文中,如果對象包含 promiseSend 方法就可以甄別為promise 對象
2. promiseSend 方法必須接受一個操作名稱,作為第一個參數
3. 操作名稱是一個可擴展的集合,下面是一些保留名稱
1. when,此時第三個參數必須是 rejection 回調。
1. rejection回調必須接受一個 rejection 原因(可以是任何值)作為第一個參數
2. get,此時第三個參數為屬性名(字符串類型)
3. put,此時第三個參數為屬性名(字符串類型),第四個參數為新屬性值。
4. del,此時第三個參數為屬性名
5. post,此時第三個參數為方法的屬性名,接下來的變參為方法的調用參數
6. isDef
4. promiseSend方法的第二個參數為 resolver 方法
5. promiseSend方法可能接受變參
6. promiseSend方法必須返回undefined

對 Promises/B 規范的補充
Promises/D 規范中對 Promises/B 規范中定義的ref、reject、def、defer方法做了進一步細致的約束,此處略去這些細節。

Promises/A+ 規范
前面提到的 Promises/A/B/D 規范都是有CommonJS組織提出的,Promises/A+是有一個自稱為Promises/A+ 組織發布的,該規范是以Promises/A作為基礎進行補充和修訂,旨在提高promise實現之間的可互操作性。

Promises/A+ 對.then方法進行細致的補充,定義了細致的Promise Resolution Procedure流程,並且將.then方法作為promise的對象甄別方法。

此外,Promises/A+ 還提供了兼容性測試工具,以確定各個實現的兼容性。

實現一個迷你版本的Promise
上面扯了這麼多規范,現在我們看看如何實現一個簡單而短小的Promise。

1、狀態機

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise() {
 // store state which can be PENDING, FULFILLED or REJECTED
 var state = PENDING;

 // store value or error once FULFILLED or REJECTED
 var value = null;

 // store sucess & failure handlers attached by calling .then or .done
 var handlers = [];
}

2、狀態變遷
僅支持兩種狀態變遷,fulfill和reject

// ...

function Promise() {
  // ...

 function fulfill(result) {
  state = FULFILLED;
  value = result;
 }

 function reject(error) {
  state = REJECTED;
  value = error;
 }

}

fulfill和reject方法較為底層,通常更高級的resolve方法開放給外部。

// ...

function Promise() {

 // ...

 function resolve(result) {
  try {
   var then = getThen(result);
   if (then) {
    doResolve(then.bind(result), resolve, reject)
    return
   }
   fulfill(result);
  } catch (e) {
   reject(e);
  }
 }
}

resolve方法可以接受一個普通值或者另一個promise作為參數,如果接受一個promise作為參數,等待其完成。promise不允許被另一個promise fulfill,所以需要開放resolve方法。resolve方法依賴一些幫助方法定義如下:

/**
 * Check if a value is a Promise and, if it is,
 * return the `then` method of that promise.
 *
 * @param {Promise|Any} value
 * @return {Function|Null}
 */
function getThen(value) {
 var t = typeof value;
 if (value && (t === 'object' || t === 'function')) {
  var then = value.then;
  if (typeof then === 'function') {
   return then;
  }
 }
 return null;
}

/**
 * Take a potentially misbehaving resolver function and make sure
 * onFulfilled and onRejected are only called once.
 *
 * Makes no guarantees about asynchrony.
 *
 * @param {Function} fn A resolver function that may not be trusted
 * @param {Function} onFulfilled
 * @param {Function} onRejected
 */
function doResolve(fn, onFulfilled, onRejected) {
 var done = false;
 try {
  fn(function (value) {
   if (done) return
   done = true
   onFulfilled(value)
  }, function (reason) {
   if (done) return
   done = true
   onRejected(reason)
  })
 } catch (ex) {
  if (done) return
  done = true
  onRejected(ex)
 }
}

這裡resolve和doResolve之間的遞歸很巧妙,用來處理promise的層層嵌套(promise的value是一個promise)。

構造器

// ...

function Promise(fn) {
  // ...
  doResolve(fn, resolve, reject);
}

.done方法

// ...
function Promise(fn) {
 // ...

 function handle(handler) {
  if (state === PENDING) {
   handlers.push(handler);
  } else {
   if (state === FULFILLED &&
    typeof handler.onFulfilled === 'function') {
    handler.onFulfilled(value);
   }
   if (state === REJECTED &&
    typeof handler.onRejected === 'function') {
    handler.onRejected(value);
   }
  }
 }

 this.done = function (onFulfilled, onRejected) {
  // ensure we are always asynchronous
  setTimeout(function () {
   handle({
    onFulfilled: onFulfilled,
    onRejected: onRejected
   });
  }, 0);
 }
 // ...
}

.then方法

// ...
function Promise(fn) {
  // ...
  this.then = function (onFulfilled, onRejected) {
   var self = this;
   return new Promise(function (resolve, reject) {
    return self.done(function (result) {
     if (typeof onFulfilled === 'function') {
      try {
       return resolve(onFulfilled(result));
      } catch (ex) {
       return reject(ex);
      }
     } else {
      return resolve(result);
     }
    }, function (error) {
     if (typeof onRejected === 'function') {
      try {
       return resolve(onRejected(error));
      } catch (ex) {
       return reject(ex);
      }
     } else {
      return reject(error);
     }
    });
   });
  }
  // ...
}

$.promise
jQuery 1.8 之前的版本,jQuery的 then 方法只是一種可以同時調用 done 、fail 和 progress 這三種回調的速寫方法,而 Promises/A 規范的 then 在行為上更像是 jQuery 的 pipe。 jQuery 1.8 修正了這個問題,使 then 成為 pipe 的同義詞。不過,由於向後兼容的問題,jQuery 的 Promise 再如何對 Promises/A 示好也不太會招人待見。

此外,在 Promises/A 規范中,由 then 方法生成的 Promise 對象是已執行還是已拒絕,取決於由 then 方法調用的那個回調是返回值還是拋出錯誤。在 JQuery 的 Promise 對象的回調中拋出錯誤是個糟糕的主意,因為錯誤不會被捕獲。

小結
最後一個例子揭示了,實現 Promise 的關鍵是實現好 doResolve 方法,在完事以後觸發回調。而為了保證異步 setTimeout(fun, 0); 是關鍵一步。

Promise 一直用得蠻順手的,其很好的優化了 NodeJS 異步處理時的代碼結構。但是對於其工作原理卻有些懵懂和好奇,於是花了些精力查閱並翻譯了Promise 的規范,以充分的理解 Promise 的細節。

以上就是關於JavaScript中Promise的使用方法介紹,希望對大家的學習有所幫助。

XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved