摘要:三模式模式其實包含兩部分和。六化在編碼的時候,想要用進行異步操作流程控制,就要將當前的異步回調函數封裝成。
一、什么是promise/deferred 模式
promise/deferred 模式是,根據promise/A 或者它的增強修改版promise/A+ 規范 實現的promise異步操作的一種實現方式。
異步的廣度使用使得回調,嵌套出現,但是一但出現深度的嵌套,就會讓coding的體驗變得相當不愉快,而且代碼后期的維護也是相當吃力的。promise/deferred模式的出現,會在一定程度上緩解這個問題。接下來我會根據promise/a 規范來介紹promise/deferred模式。
(題外話:什么是規范,規范其實就相當于制定的規則,但卻沒有在代碼層面上有默認的具體實現)
promise/a 提議對單個異步操作作出了這樣的抽象定義:
1.promise操作只會在以下3種狀態中的一種:等待態(Pending)、執行態(Fulfilled)和拒絕態(Rejected)。
2.promise的狀態只會出現從等待狀態向執行態或者拒絕態轉化,不可以逆轉。執行態和拒絕態之間不能相互轉換
3.promise狀態一旦被轉換,就不能被更改。
4.在api上,規范定義比較簡單,只要求promise 必修提供有一個then方法,以訪問當前值、最終值和拒絕原因
then方法接受兩個參數
promise.then(onFulfilled,onRejected)
5.then方法的onFulfilled,onRejected 方法都是可選參數,且不是function,都被忽略
6.then()方法返回promise對象,以實現鏈式寫法。
promise/deferred 模式 其實包含兩部分:Promise 和 Deferred。
Deferred主要是用于內部,來維護異步模型的狀態。
Promise只要用于外部,通過then()方法,暴露給外部調用,以添加業務邏輯和業務的組裝。
promise 和 deferred的關系圖
從圖中可以看到:
1.deferred對象通過resolve方法,改變自身狀態為執行態,并觸發then()方法的onfulfilled回調函數 2.deferred對象通過reject方法,改變自身狀態為拒絕態,并觸發then()方法的onrejected回調函數
下面 我們就用代碼來實現一下:
/** * Promise 類 * @constructor */ function Promise() { this.handler = {}; } /** * promise 對象的then方法 * @param onFulfilled 當 promise 執行結束后其必須被調用,其第一個參數為 promise 的終值,其調用次數不可超過一次 * @param onRejected 當 promise 被拒絕執行后其必須被調用,其第一個參數為 promise 的據因,其調用次數不可超過一次 * @returns {Promise} 規范定義必修返回 primise對象 */ Promise.prototype.then = function (onFulfilled, onRejected) { var handler = {} if (typeof onFulfilled === "function") { handler.resolve = onFulfilled } if (typeof onRejected === "function") { handler.reject = onRejected } this.handler = handler return this }
這里可以看到then方法所做的事情就是講回調函數存放起來,為了完成整個流程,還需要觸發執行這些回調函數的地方,而實現這些功能的對象就叫做deferred(延遲對象)。示范代碼如下
function Deferred() { /* 狀態:默認 等待態 pending */ this.state = "pending"; this.promise = new Promise() } Deferred.prototype.resolve = function (obj) { this.state = "fulfilled" var handler = this.promise.handler if (handler && handler.resolve) { handler.resolve(obj) } } Deferred.prototype.reject = function (obj) { this.state = "rejected" var handler = this.promise.handler if (handler && handler.reject) { handler.reject(obj) } }
以上已經定義好了Promies 和Deferred ,那我們怎么對一個異步操作函數進行封裝呢?
假如我們有這樣的異步函數
function asyncDoSomeing(flag, message) { setTimeout(function () { if (flag) { return message } }, 3000) }
對其封裝的代碼就是
function asyncDoSomeing(flag, message) { var deferred = new Deferred() setTimeout(function () { if (flag) { deferred.resolve(message) } else { deferred.reject({code: 400, message: "拒絕"}) } }, 3000) return deferred.promise }
最后我們就可以這么使用了
asyncDoSomeing(true, "測試執行成功").then(function (message) { console.log(message) }, function (err) { console.log(err) })
到這里只是單個promise對象的簡單異步的操作控制,但是有熟悉node.js 和angular.js 的同學就會發現,這個寫法跟node.js 里面的一個異步控制流程 q 模塊(https://github.com/kriskowal/q )寫法是一樣的。是的哦 它就是promise/deferred 模式。隨便提一下 Angularjs的$q對象是q的精簡版。
四、鏈式調用做到以上的簡單實現,理想的coding方式,應該前一個調用結果作為下一個調用的輸入,這就是鏈式調用。
為了避免回調地獄,可以借鑒jquery的鏈式寫法。
$("#tab").eq($(this).index()).show().siblings().hide();
鏈式寫法的核心在于,每個方法都返回 自身 this。
我們現在需要實現promise的鏈式調用,前一個調用結果作為下一個調用的輸入
step1.then(step2).then(step3)
現在我們實現的then方法確實是返回this的,也就是promise本身,是可以實現鏈式的。
但是前一個調用的結果卻做不到是下一個調用的輸入
下面來改造一下上面的代碼,讓他實現這個要求。
function Promise() { this.handlerQueue = []; this.isPromise = true }
1.將原本的handler對象改為 一個數組,存放所有then方法的回調。
Promise.prototype.then = function (onFulfilled, onRejected) { var handler = {} if (typeof onFulfilled === "function") { handler.resolve = onFulfilled } if (typeof onRejected === "function") { handler.reject = onRejected } this.handlerQueue.push(handler) return this } function Deferred() { this.state = "pending" this.promise = new Promise() } Deferred.prototype.resolve = function (obj) { this.state = "fulfilled" var promise = this.promise var handler = {} while (handler = promise.handlerQueue.shift()) { if (handler && handler.resolve) { var res = handler.resolve(obj) if (res && res.isPromise) { res.handlerQueue = promise.handlerQueue this.promise = res return; } else { obj = res } } } } Deferred.prototype.reject = function (obj) { this.state = "rejected" var promise = this.promise var handler = {} while (handler = promise.handlerQueue.shift()) { if (handler && handler.reject) { var res = handler.reject(obj) if (res && res.isPromise) { res.handlerQueue = promise.handlerQueue this.promise = res return; } else { obj = res } } } } //------ test-------// function asyncDosomeing(flag, name) { const deferred = new Deferred() setTimeout(function () { if (flag) { deferred.resolve({code: 200, message: "成功", name: name}) } else { deferred.reject({code: 400, message: "失敗", name: name}) } }, 2000) return deferred.promise } asyncDosomeing(true, "asyncDosomeing1").then(result => { console.info(result) return asyncDosomeing(false, "asyncDosomeing2") }).then(result => { console.info(result) return "dadds" }).then(result => { console.info(result) })五、統一的異常處理(拒絕處理)
那現在,我們有個需求,想實現所有的拒絕統一在一個地方處理。而不是每個then方法都傳一個rejected 回調,只希望then()方法可以,安安心心的處理成功的回調。
step1().then(step2).then(step3).catch(function(err){ // do something when err })
加一個catch err 的回調,當出現異常就直接到這個流程上處理。
那我們就在promise 的原型上架一個catch方法,如下
Promise.prototype.catch = function (onRejected) {
var handler = {} if (typeof onRejected === "function") { handler.reject = onRejected } this.handlerQueue.push(handler) return this } //------ test-------// function asyncDosomeing(flag, name) { const deferred = new Deferred() setTimeout(function () { if (flag) { deferred.resolve({code: 200, message: "成功", name: name}) } else { deferred.reject({code: 400, message: "失敗", name: name}) } }, 2000) return deferred.promise } asyncDosomeing(true, "asyncDosomeing1").then(result => { console.info(result) return asyncDosomeing(false, "asyncDosomeing2") }).then(result => { console.info(result) return "dadds" }).then(result => { console.info(result) }).catch(err => { console.info("catch") console.info(err) return asyncDosomeing(true, "asyncDosomeing3----catch") })
這樣就可以實現,只要異步操作流程中有一步被拒絕,下面流程就自然中斷,直接到catch回調中處理異常。
六、API Promise化在編碼的時候,想要用promise進行異步操作流程控制,就要將當前的異步回調函數封裝成promise。在自己開發的時候,往往會去引用第三方的模塊,然后發現這些模塊的異步回調API 不支持promise寫法。難道我們自己全部封裝實現一遍?!這明顯是不合理的。那我們就可以實現一個 方法可以批量將方法Promise化,相關代碼如下:
1.在deferred原型上實現一個異步回調函數,回調執行后觸發deferred resolve 和 reject的方法
Deferred.prototype.callBack = function () { var that = this return function (err, result) { if (err) { that.reject(err) } else { that.resolve(result) } } } 2.定義一個Api Promise化 方法 /** * 將異步操作轉換成promise */ var promisify = function (method) { if (typeof method !== "function") { throw new TypeError("is not a function") } return function () { const defrred = new Deferred() var args = Array.prototype.slice.call(arguments, 0) // 克隆參數 args.push(defrred.callBack()) method.apply(this, args) return defrred.promise } }
最后我們就可以簡化代碼
var readFile = promisify(fs.readFile); readFile("file.text").then(function(file){ return readFile(file.trim()) }).then(function(file2){ console.log(file2) })
這里只是對promise/deferred 原理的簡單實現,還有很多情況沒有考慮。希望大家在做promise異步流程操作的時候,還是選擇現在成熟的模塊。比如 q模塊、bulebird、when、或者 es6 的promise 去做。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/87136.html
摘要:簡介項目命名為就是一個服務器單純開發一個服務器的想法,變成構建網絡應用的一個基本框架發展為一個強制不共享任何資源的單線程,單進程系統。單線程弱點無法利用多核錯誤會引起整個應用退出,應用的健壯性大量計算占用導致無法繼續調用異步。 NodeJs簡介 Ryan Dahl項目命名為:web.js 就是一個Web服務器.單純開發一個Web服務器的想法,變成構建網絡應用的一個基本框架.Node發展...
摘要:為的項,取出來的分別為和,所以上的和方法,調用的是中的方法,實質是往各自的回調列表中添加回調函數。進度回調函數數組。參數為異步對象的索引值,參數為對應的上下文數組,即或,為對應的回調函數數組,即或。 Deferred 模塊也不是必備的模塊,但是 ajax 模塊中,要用到 promise 風格,必需引入 Deferred 模塊。Deferred 也用到了上一篇文章《讀Zepto源碼之C...
摘要:所謂的能對狀態進行操作的特權方法,指的就是能對對象的狀態進行等調用的方法,而通常的的話只能在通過構造函數傳遞的方法之內對對象的狀態進行操作。一般會在構造函數中編寫邏輯,什么時候執行回調,什么時候執行回調。 原文地址 1. 在then中使用reject 如果一個promise最初只定義了resolve,但是還想要使用reject怎么辦? 可以在then中返回一個新的promise。這個...
摘要:的異步完成整個異步環節的有事件循環觀察者請求對象以及線程池。執行回調組裝好請求對象送入線程池等待執行,實際上是完成了異步的第一部分,回調通知是第二部分。異步編程是首個將異步大規模帶到應用層面的平臺。 showImg(https://segmentfault.com/img/remote/1460000011303472); 本文首發在個人博客:http://muyunyun.cn/po...
摘要:方法完成回調注冊模式下,對象通過方法調用,注冊完成態和失敗態的回調函數。這些回調函數組成一個回調隊列,處理的值。調用實例的方法,能使注冊的回調隊列中的回調函數依次執行。 之前寫了一篇關于ES6原生Promise的文章。近期又讀樸靈的《深入淺出Node》,里面介紹了一個Promise/Deferred模式。 Promise是解決異步問題的利器。它其實是一種模式。Promise有三種狀態,...
閱讀 2850·2023-04-26 01:02
閱讀 1875·2021-11-17 09:38
閱讀 800·2021-09-22 15:54
閱讀 2906·2021-09-22 15:29
閱讀 893·2021-09-22 10:02
閱讀 3444·2019-08-30 15:54
閱讀 2013·2019-08-30 15:44
閱讀 1603·2019-08-26 13:46