摘要:近幾年隨著開發模式的逐漸成熟,規范順勢而生,其中就包括提出了規范,完全改變了異步編程的寫法,讓異步編程變得十分的易于理解。最后,是如此的優雅但也只是解決了回調的深層嵌套的問題,真正簡化異步編程的還是,在端,建議考慮。
前段時間頻頻看到Promise這個詞,今天發現騰訊AlloyTeam寫得這篇很贊,遂轉之。 原文鏈接
本篇,主要普及promise的用法。
一直以來,JavaScript處理異步都是以callback的方式,在前端開發領域callback機制幾乎深入人心。在設計API的時候,不管是瀏覽器廠商還是SDK開發商亦或是各種類庫的作者,基本上都已經遵循著callback的套路。
近幾年隨著JavaScript開發模式的逐漸成熟,CommonJS規范順勢而生,其中就包括提出了Promise規范,Promise完全改變了js異步編程的寫法,讓異步編程變得十分的易于理解。
在callback的模型里邊,我們假設需要執行一個異步隊列,代碼看起來可能像這樣:
loadImg("a.jpg", function() { loadImg("b.jpg", function() { loadImg("c.jpg", function() { console.log("all done!"); }); }); });
這也就是我們常說的回調金字塔,當異步的任務很多的時候,維護大量的callback將是一場災難。當今Node.js大熱,好像很多團隊都要用它來做點東西以沾沾“洋氣”,曾經跟一個運維的同學聊天,他們也是打算使用Node.js做一些事情,可是一想到js的層層回調就望而卻步。
好,扯淡完畢,下面進入正題。
Promise可能大家都不陌生,因為Promise規范已經出來好一段時間了,同時Promise也已經納入了ES6,而且高版本的chrome、firefox瀏覽器都已經原生實現了Promise,只不過和現如今流行的類Promise類庫相比少些API。
所謂Promise,字面上可以理解為“承諾”,就是說A調用B,B返回一個“承諾”給A,然后A就可以在寫計劃的時候這么寫:當B返回結果給我的時候,A執行方案S1,反之如果B因為什么原因沒有給到A想要的結果,那么A執行應急方案S2,這樣一來,所有的潛在風險都在A的可控范圍之內了。
上面這句話,翻譯成代碼類似:
var resB = B(); var runA = function() { resB.then(execS1, execS2); }; runA();
只看上面這行代碼,好像看不出什么特別之處。但現實情況可能比這個復雜許多,A要完成一件事,可能要依賴不止B一個人的響應,可能需要同時向多個人詢問,當收到所有的應答之后再執行下一步的方案。最終翻譯成代碼可能像這樣:
var resB = B(); var resC = C(); ... var runA = function() { reqB .then(resC, execS2) .then(resD, execS3) .then(resE, execS4) ... .then(execS1); }; runA();
在這里,當每一個被詢問者做出不符合預期的應答時都用了不同的處理機制。事實上,Promise規范沒有要求這樣做,你甚至可以不做任何的處理(即不傳入then的第二個參數)或者統一處理。
好了,下面我們來認識下Promise/A+規范:
一個 promise 可能有三種狀態:等待(pending)、已完成(fulfilled)、已拒絕(rejected)
一個 promise 的狀態只可能從“等待”轉到“完成”態或者“拒絕”態,不能逆向轉換,同時“完成”態和“拒絕”態不能相互轉換
promise 必須實現then方法(可以說,then 就是 promise 的核心),而且then 必須返回一個 promise,同一個 promise 的 then 可以調用多次,并且回調的執行順序跟它們被定義時的順序一致
then 方法接受兩個參數,第一個參數是成功時的回調,在 promise 由“等待”態轉換到“完成”態時調用,另一個是失敗時的回調,在 promise 由“等待”態轉換到“拒絕”態時調用。同時,then 可以接受另一個 promise 傳入,也接受一個“類then”的對象或方法,即 thenable 對象。
可以看到,Promise規范的內容并不算多,大家可以試著自己實現以下Promise。
以下是筆者自己在參考許多類Promise庫之后簡單實現的一個Promise,代碼請移步PromiseA.
簡單分析下思路:
構造函數Promise接受一個函數resolver,可以理解為傳入一個異步任務,resolver接受兩個參數,一個是成功時的回調,一個是失敗時的回調,這兩參數和通過then傳入的參數是對等的。
其次是then的實現,由于Promise要求then必須返回一個promise,所以在then調用的時候會新生成一個promise,掛在當前promise的next上,同一個promise多次調用都只會返回之前生成的next。
由于then方法接受的兩個參數都是可選的,而且類型也沒限制,可以是函數,也可以是一個具體的值,還可以是另一個promise。下面是then的具體實現:
Promise.prototype.then = function(resolve, reject) { var next = this._next || (this._next = Promise()); var status = this.status; var x; if("pending" === status) { isFn(resolve) && this._resolves.push(resolve); isFn(reject) && this._rejects.push(reject); return next; } if("resolved" === status) { if(!isFn(resolve)) { next.resolve(resolve); } else { try { x = resolve(this.value); resolveX(next, x); } catch(e) { this.reject(e); } } return next; } if("rejected" === status) { if(!isFn(reject)) { next.reject(reject); } else { try { x = reject(this.reason); resolveX(next, x); } catch(e) { this.reject(e); } } return next; } };
這里,then做了簡化,其他promise類庫的實現比這個要復雜得多,同時功能也更多,比如還有第三個參數——notify,表示promise當前的進度,這在設計文件上傳等時很有用。對then的各種參數的處理是最復雜的部分,有興趣的同學可以參看其他類Promise庫的實現。
在then的基礎上,應該還需要至少兩個方法,分別是完成promise的狀態從pending到resolved或rejected的轉換,同時執行相應的回調隊列,即resolve()和reject()方法。
到此,一個簡單的promise就設計完成了,下面簡單實現下兩個promise化的函數:
function sleep(ms) { return function(v) { var p = Promise(); setTimeout(function() { p.resolve(v); }, ms); return p; }; }; function getImg(url) { var p = Promise(); var img = new Image(); img.onload = function() { p.resolve(this); }; img.onerror = function(err) { p.reject(err); }; img.url = url; return p; };
由于Promise構造函數接受一個異步任務作為參數,所以getImg還可以這樣調用:
function getImg(url) { return Promise(function(resolve, reject) { var img = new Image(); img.onload = function() { resolve(this); }; img.onerror = function(err) { reject(err); }; img.url = url; }); };
接下來(見證奇跡的時刻),假設有一個BT的需求要這么實現:異步獲取一個json配置,解析json數據拿到里邊的圖片,然后按順序隊列加載圖片,每張圖片加載時給個loading效果。
function addImg(img) { $("#list").find("> li:last-child").html("").append(img); }; function prepend() { $("
這里的sleep只是為了看效果加的,可猛擊查看demo!當然,Node.js的例子可查看這里。
在這里,Promise.resolve(v)靜態方法只是簡單返回一個以v為肯定結果的promise,v可不傳入,也可以是一個函數或者是一個包含then方法的對象或函數(即thenable)。
類似的靜態方法還有Promise.cast(promise),生成一個以promise為肯定結果的promise;
Promise.reject(reason),生成一個以reason為否定結果的promise。
我們實際的使用場景可能很復雜,往往需要多個異步的任務穿插執行,并行或者串行同在。這時候,可以對Promise進行各種擴展,比如實現Promise.all(),接受promises隊列并等待他們完成再繼續,再比如Promise.any(),promises隊列中有任何一個處于完成態時即觸發下一步操作。
標準的Promise
可參考html5rocks的這篇文章JavaScript Promises,目前高級瀏覽器如chrome、firefox都已經內置了Promise對象,提供更多的操作接口,比如Promise.all(),支持傳入一個promises數組,當所有promises都完成時執行then,還有就是更加友好強大的異常捕獲,應對日常的異步編程,應該足夠了。
第三方庫的Promise
現今流行的各大js庫,幾乎都不同程度的實現了Promise,如dojo,jQuery、Zepto、when.js、Q等,只是暴露出來的大都是Deferred對象,以jQuery(Zepto類似)為例,實現上面的getImg():
function getImg(url) { var def = $.Deferred(); var img = new Image(); img.onload = function() { def.resolve(this); }; img.onerror = function(err) { def.reject(err); }; img.src = url; return def.promise(); };
當然,jQuery中,很多的操作都返回的是Deferred或promise,如animate、ajax:
// animate $(".box") .animate({"opacity": 0}, 1000) .promise() .then(function() { console.log("done"); }); // ajax $.ajax(options).then(success, fail); $.ajax(options).done(success).fail(fail); // ajax queue $.when($.ajax(options1), $.ajax(options2)) .then(function() { console.log("all done."); }, function() { console.error("There something wrong."); });
Query還實現了done()和fail()方法,其實都是then方法的shortcut。
處理promises隊列,jQuery實現的是$.when()方法,用法和Promise.all()類似。
其他類庫,這里值得一提的是when.js,本身代碼不多,完整實現Promise,同時支持browser和Node.js,而且提供更加豐富的API,是個不錯的選擇。這里限于篇幅,不再展開。
尾聲
我們看到,不管Promise實現怎么復雜,但是它的用法卻很簡單,組織的代碼很清晰,從此不用再受callback的折磨了。
最后,Promise是如此的優雅!但Promise也只是解決了回調的深層嵌套的問題,真正簡化JavaScript異步編程的還是Generator,在Node.js端,建議考慮Generator。
下一篇,研究下Generator。
github原文: https://github.com/chemdemo/chemdemo.github.io/issues/6
參考文獻
JavaScript Promises
JavaScript Promises(中文)
when.js
Asynch JS: The Power Of $.Deferred
jQuery: $.Deferred()
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/85902.html
摘要:近幾年隨著開發模式的逐漸成熟,規范順勢而生,其中就包括提出了規范,完全改變了異步編程的寫法,讓異步編程變得十分的易于理解。最后,是如此的優雅但也只是解決了回調的深層嵌套的問題,真正簡化異步編程的還是,在端,建議考慮。 本篇,簡單實現一個promise,主要普及promise的用法。 一直以來,JavaScript處理異步都是以callback的方式,在前端開發領域callback機制...
摘要:另一種關于組件的常見說法,是組件是為了重用。這件事情是前端特有的,受限制于的結構。這一節的題目叫做混亂的組件通訊,我們來仔細掰扯一下細節,因為組件模型雖然很常說但是對通訊過程沒有約定。 這個話題很難寫。 但是反過來說,愛因斯坦有句名言:如果你不能把一個問題向一個六歲孩子解釋清楚,那么你不真的明白它。 所以解釋清楚一個問題的關鍵,不是去擴大化,而是相反,最小化。 Lets begin. ...
摘要:坑無視和是十分特殊的事件,要求事件處理函數內部不能阻塞當前線程,而卻恰恰就會阻塞當前線程,因此規范中以明確在和中直接無視這幾個方法的調用。 前言 ?最近實施的同事報障,說用戶審批流程后直接關閉瀏覽器,操作十余次后系統就報用戶會話數超過上限,咨詢4A同事后得知登陸后需要顯式調用登出API才能清理4A端,否則必然會超出會話上限。?即使在頁面上增添一個登出按鈕也無法保證用戶不會直接關掉瀏覽器...
摘要:前端日報精選入門指南入口,輸出,加載器和插件中數據類型轉換讓我印象深刻的面試題大話大前端時代一與的組件化庖丁解牛一發布中文第期手把手教你用管理狀態上個快速編程技巧眾成翻譯中執行順序組件解耦之道眾成翻譯組件模型啟示錄有個梨作 2017-07-10 前端日報 精選 Webpack入門指南: 入口,輸出,加載器和插件JavaScript中數據類型轉換讓我印象深刻的javascript面試題大...
摘要:前端日報精選十問幫你理清前端工程師及大前端團隊的成長問題譯讀完細則之后學到的件事掘金怎么寫一個組件庫一眾成翻譯還有這操作一個能生成思維導圖的開源搜索引擎知乎專欄中文前端推薦第天值得收藏的基礎教程知乎專欄第期沒有的一天轉載中回調地 2017-06-15 前端日報 精選 十問sofish:幫你理清前端工程師及大前端團隊的成長問題![譯] 讀完 flexbox 細則之后學到的 11 件事 -...
閱讀 2181·2021-11-24 10:26
閱讀 2792·2021-11-23 09:51
閱讀 2907·2021-10-08 10:05
閱讀 1683·2021-09-22 15:18
閱讀 1619·2019-08-29 18:45
閱讀 2143·2019-08-29 18:40
閱讀 3332·2019-08-29 16:16
閱讀 2849·2019-08-29 14:21