Promise與異步
來自: http://ouvens.github.io/frontend-javascript/2016/03/04/promise-and-asyncjs.html
接觸過promise的的都知道它的應用場景和用途,Promise可以用來避免異步操作函數里的嵌套回調(callback hell)問題,因為解決異步最直接的方法是回調嵌套,將后一個的操作放在前一個操作的異步回調里,但如果操作多了,就會有很多層的嵌套。
Promise的實現方式比較多,有豐富的第三方庫,ES6也已經原生支持了Promise,jquery中也有$.Deferred()等可以解決異步嵌套問題。
先給下Promise學術點的 描述 :
promise代表一個異步操作的執行返回狀態,這個執行返回狀態在promise對象創建時未必已知。它允許你為異步操作的成功或失敗指定處理方法。 這使得異步方法可以像同步方法那樣返回值:異步方法會返回一個包含了原返回狀態的 promise 對象來替代原返回狀態。
一、Promise的適用場景
Promise并非適用于所有的異步場景,例如事件的綁定,某個程度上Promise有點類似事件的監聽回調,當觸發某個操作時進行后面特定的邏輯。但Promise只能執行一次,且需要前面特定的操作執行完成才會進行下一步,一般分成功和失敗兩種場景,成功或失敗后會立即執行響應函數。這就很適合判斷一個比較耗時的操作是否最終執行成功的場景,就如我們通常理解的ajax網絡請求、讀取localstorage等操作。
二、Promise的表現
如果使用回調方法處理多個操作的異步場景,判斷某個操作成功或失敗的控制在于聲明的匿名函數里面,使用Promise對象則可以重新定義異步執行的狀態和控制邏輯。
promises的最重要的特點就是它把我們處理任何函數調用的成功或者失敗的方式規范成了可預測的形式,特別是如果這個調用實際上的異步的。
Promise中有幾個狀態:
-
pending: 初始狀態。 非 fulfilled 或 rejected。
-
resolved: 成功的操作。也有的成為fulfilled 。
-
rejected: 失敗的操作。
不同的Promise差異基本表現如下:
-
構造Promise對象 new Promise().resolve() 或者 new Pomise(function(resolve, reject) {})
-
是否有 .done() .fail() .always() 等方法
-
是否有Promise.all()方法
-
是否有isRejected() isResolved()
-
.then() return 結果鏈式的
三、幾種規范的promise
2.1、Promise的Promise/A 規范和Promise/A+規范
先看下規范的地址: http://wiki.commonjs.org/wiki/Promises/A https://promisesaplus.com/
什么是A+規范的Promise? Promises/A+是在Promises/A的基礎上對原有規范進行修正和增強。
Promise A+與Promise A的主要區別:
-
符合Promise/A+規范的promise實現均以then方法為交互核心。Promises/A+組織會因新發現的問題以向后兼容的方式修改規范,因此Promises/A+規范相對來說還是比較穩定的。
-
A+規范強調了不同實現之間的互操作和混用,通過術語thenable來區分promise對象,當一個對象擁有then函數就認為是promise對象
-
A+定義當onFulfilled或者onRejected返回promise時后的處理過程,他們必須是作為函數來調用,而且調用過程必須是異步的
-
A+嚴格定義了then方法鏈式調用時onFulfilled或者onRejected的調用順序
目前判斷是否為Promise/ A+規范主要看Promiise的方法含有new Pomise(function(resolve, reject) {})、then、resolve、all等方法。ES6 Promise的實現嚴格遵循了Promise/A+規范。例如Defferd就不是Promise/ A+的規范。
2.2、Defferd實現規范
比較典型的是jquery的Defferd方法實現的Promise,另外jquery還有一個Promise的類型,實現的原理相同,但是不遵循Promise/A +規范,相對于Promise沒有那么穩定。
我們先來看看jquery的Promise是怎樣實現的。我們看下jquery的Deferred實現源碼:
// 精簡后主要邏輯的源碼 Deferred: function( func ) { var tuples = [ // action, add listener, listener list, final state [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], [ "notify", "progress", jQuery.Callbacks("memory") ] ], state = "pending", promise = { state: function() {}, always: function() {}, then: function( /* fnDone, fnFail, fnProgress */ ) { }, // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object promise: function( obj ) {} }, deferred = {}; jQuery.each( tuples, function( i, tuple ) { deferred[ tuple[0] + "With" ] = list.fireWith; }); promise.promise( deferred ); // All done! return deferred; }, // 調用時間訂閱方法 var def = $.Deferred(); def.done(function(){ console.log(“成功”); }).fail(function(){ console.log(“失敗”); }).catch(function(){ console.log(“再一次成功”); })
可見,jquery的Deferred是個工廠類,返回的是內部構建的deferred對象;tuples 含有三個$.Callbacks對象,分別表示成功,失敗,處理中三種狀態;創建的promise對象,具有state、always、then、primise方法;擴展primise對象生成最終的Deferred對象,返回該對象;沒有resolve、reject、all等Promise/A+ 規范的常用方法。
三、兼容性
目前使用需要使用polyfill,也就是原生實現一個Promise支持較低瀏覽器,第三方實現庫很多后面給了個學習的較好例子。
四、generator的異步
Promise處理異步問題相信都了解了。ES6里的generator還有另一個處理異步的方法,那ES6定義這兩個特性豈不是重復了?
單獨地介紹Generator沒有太大價值,因為它除了更復雜外,功能與普通函數沒有太大差別。真正讓Generator具有價值的是yield關鍵字,這個yield關鍵字讓Generator內部的邏輯能夠切割成多個部分。并且可以靈活控制內部的執行情況。
// 申明要用 var gen = function* (){} 的方式 var compute = function* (a, b) { yield console.log(a + b); yield console.log(a - b); yield console.log(a * b); yield console.log(a / b); }; var generator = compute(4, 2); generator.next(); // 6 generator.next(); // 2 generator.next(); // 8 generator.next(); // 2
運行時使用node –harmony-generators test.js
不難發現它的運行過程,generator函數運行到yield時會停止,等待下一個next()方法調用讓它繼續執行。我們改下成為異步方法,異步我們需要借助高階函數
var helper = function(fn) { return function() { var args = [].slice.call(arguments); var pass; args.push(function() { // 在回調函數中植入收集邏輯 if (pass) { pass.apply(null, arguments); } }); fn.apply(null, args); return function(fn) { // 傳入一個收集函數 pass = fn; }; }; };
那么后面的寫法做下修改
var sum = helper(function sum(a, b){ console.log(a + b); }); var minus = helper(function minus(a, b){ console.log(a - b); }); var muti= helper(function muti(a, b){ console.log(a * b); }); var devide= helper(function devide(a, b){ console.log(a / b); }); var compute = function*(a, b) { yield sum(a, b); yield minus(a, b); yield muti(a, b); yield devide(a, b); }; var generator = compute(5, 2); var state = generator.next(); /** * next 返回 {value:'',done: false}的結構,value表示執行傳入的值,done表示是否結束 * @param {[type]} !state.done [description] * @return {[type]} [description] */ setTimeout(function() { while (!state.done) { state = generator.next(); } }, 100); console.log('other');
generator實現異步的方法也有比較完整的封裝方式,實現先可以看:https://github.com/ouvens/co 可以看個簡單版的:
var co = function(flow) { var generator = flow(); var next = function(data) { var result = generator.next(data); if (!result.done) { result.value(function(err, data) { if (err) { throw err; } next(data); }); } }; next(); };
我們小結一下通過Generator進行流程控制的特點。 - 每個異步方法都需要標準化為yield關鍵字能接受的方法,使我們有機會注入特殊邏輯,這個過程被稱為thunkify。 - 需要巧妙地將異步調用執行完成得到的結果通過.next()傳遞給下一段流程。 - 需要遞歸地將業務邏輯執行完成。
需要注意的是yield只能暫停Generator內部的邏輯,它并不是真正暫停整個線程,Generator外的業務邏輯依然會繼續執行下去。
五、總結
實現異步的方法目前有,自定義嵌套,Promise、generator、Defferd,還有ES7的async(其實是generator的封裝),不同場景可以選擇不同的方式去實現
簡單的Promise實現樣例:https://github.com/ouvens/promise
generator異步實現:https://github.com/tj/co
參考文章:http://www.infoq.com/cn/articles/generator-and-asynchronous-programming/ https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise http://www.shaynegui.com/promise-aplus-implementation/ http://www.html5rocks.com/zh/tutorials/es6/promises/ https://blog.domenic.me/youre-missing-the-point-of-promises/#toc_1 https://github.com/nodejs/node-v0.x-archive/wiki/modules#async-flow http://www.html-js.com/article/JavaScript-tips-on-how-to-implement-a-ECMAScript-6-promise-patch
</article>