細說 JavaScript 中的 Promise
一、前言
JavaScript是單執行緒的,固,一次只能執行一個任務,當有一個任務耗時很長時,後面的任務就必須等待。那麼,有什麼辦法,可以解決這類問題呢?(拋開WebWorker不談),那就是讓程式碼非同步執行嘛。什麼意思,如Ajax非同步請求時,就是通過不斷監聽readyState的值,以確定執行指定的回撥函式。
通常的非同步執行有三種,回撥函式、事件監聽以及釋出訂閱,其中事件監聽和釋出訂閱其實差不多,只是後者更加健壯一些。
如回撥函式,回撥函式是應用在非同步執行中最簡單的程式設計思想。如下:
function async(item,callback){ console.log(item); setTimeout(function(){ callback(item+1); },1000); }
在上述列子中,執行async函式時,完成列印操作,並在1秒後執行callback回撥函式(但不一定是1秒,詳情見”setTimeout那些事兒”)。
非同步的主要目的就是處理非阻塞,提升效能。想象一下,如果某個操作需要經過多個async函式操作呢,如下:
async(1, function(item){ async(item, function(item){ async(item, function(item){ console.log('To be continued..'); }); }); });
是不是有點不易閱讀了?
再比如,為了讓上述程式碼更加健壯,我們可以加入異常捕獲。在非同步的方式下,異常處理分佈在不同的回撥函式中,我們無法在呼叫的時候通過try…catch的方式來處理異常, 所以很難做到有效,清楚。
哎喲喂,那可怎麼辦呢?
噔噔噔噔,噔噔噔噔—Promise閃亮登場。
倘若用ES6的Promise優化上述程式碼,可得:
function opration(item){ var p = new Promise(function(resolve, reject){ setTimeout(function(){ resolve(item+1); },1000); }); console.log(item); return p; } function failed(e){ console.log(e); } Promise.resolve(1).then(opration).then(opration).then(opration).catch(failed);
用Promise 優化後的程式碼,優點顯而易見,讓回撥函式變成了鏈式呼叫,避免了層層巢狀,使程式流程變得清晰明朗,併為一個或者多個回撥函式丟擲的錯誤通過catch方法進行統一處理。
哎呦,不錯嘛,那這個ES6中的Promise到底是何方聖神,具體使用法則是什麼呢?我們就一起來探究探究。
二、Promise概述
Promise是非同步程式設計的一種解決方案,比傳統的解決方案(回撥和事件)更合理和更強大。它由社群最早提出和實現,ES6將其寫進了語言標準,統一了用法,原生提供了Promise物件。
Promise物件有且只有三種狀態:
1、 pending:非同步操作未完成。
2、 resolved:非同步操作已完成。
3、 rejected:非同步操作失敗。
又,這三種狀態的變化只有兩種模式,並且一旦狀態改變,就不會再變:
1、非同步操作從pending到resolved;
2、非同步操作從pending到rejected;
好了,既然它是屬於ES6規範,我們再通過chrome,直接列印出Promise,看看這玩意:
恩,一目瞭然,Promise為建構函式,歐克,這樣通過它,我們就可以例項化自己的Promise物件了,並加以利用。
三、Promise入門指南
Promise既然是一個建構函式,那麼我們就先new一個看看。如下:
var p = new Promise();
並執行上述程式碼,chrome截圖如下:
怎麼報錯了呢?
哦,對了,Promise建構函式,需要一個函式作為其引數哦,並且作為引數的函式中,有兩個引數,第一個引數的作用為,當非同步操作從pending到resolved時,供其呼叫;第二個引數的作用為,當非同步操作從pending到rejected時,供其呼叫。
例如,我將匿名函式的第一個引數取名為resolve;第二個引數取名為reject。Demo如下:
var p = new Promise(function(resolve, reject){ console.log('new一個Promise物件'); setTimeout(function(){ resolve('Monkey'); },1000); });
並執行上述程式碼,chrome截圖如下:
特別提醒:當傳入匿名函式作為建構函式Promise的引數時,我們在new的時候,匿名函式就已經執行了,如上圖。
咦,上述程式碼中,我們在匿名函式中,通過setTimeout定時器,在1秒後,還呼叫了resolve呢,怎麼沒有報undefined或者錯誤呢?!
這就是Promise強大之處的一點。正因為這樣,我們就可以將非同步操作改寫成優雅的鏈式呼叫。怎麼呼叫呢?
還記得,我們在“Promise概述”一小節中,通過chrome列印Promise,用紅線框中的區域麼?其中,Promise原型中有一then方法(Promise.prototype.then),通過這個then方法,就可以了。如下:
p.then(function(value){ console.log(value); });
其中,then方法有兩個匿名函式作為其引數,與Promise的resolve和reject引數一一對應。執行程式碼,結果如下:
好了,當then執行完後,如果我們想繼續在其之後看,使用then方法鏈式呼叫,有兩種情況,一種是直接返回非Promise物件的結果;另一種是返回Promise物件的結果。
1、返回非Promise物件的結果:緊跟著的then方法,resolve立刻執行。並可使用前一個then方法返回的結果。如下:
p.then(function(value){ console.log(value); //返回非Promise物件,如我的物件 return { name: 'Dorie', age: 18 }; }).then(function(obj){ console.log(obj.name); });
執行上述完整程式碼,chrome截圖如下:
2、返回Promise物件的結果:緊跟著的then方法,與new Promise後的then方法一樣,需等待前面的非同步執行完後,resolve方可被執行。如下:
p.then(function(value){ var p = new Promise(function(resolve, reject){ setTimeout(function(){ var message = value + ' V Dorie' resolve(message); },1000); }); console.log(value); //返回一個Promise物件 return p; }).then(function(value){ console.log(value); });
執行上述完整程式碼,chrome截圖如下:
那麼,當建立、執行Promise方法中有異常報錯,如何捕獲呢?
Promise.prototype.catch原型方法,就是為其而設定的。它具有冒泡的特性,比如當建立Promise例項時,就出錯了,錯誤訊息就會通過鏈式呼叫的這條鏈,一直追溯到catch方法,如果找到盡頭都沒有,就報錯,並且再找到catch之前的所有then方法都不能執行了。Demo如下(程式碼太長,請自行展開):
<!DOCTYPE html> <head> <title>test</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> </head> <body> <script> var p = new Promise(function(resolve, reject){ //M未定義 console.log(M); setTimeout(function(){ resolve('Monkey'); },1000); }); p.then(function(value){ var p = new Promise(function(resolve, reject){ setTimeout(function(){ var message = value + ' V Dorie' resolve(message); },1000); }); console.log(value); //返回一個Promise物件 return p; }).then(function(value){ console.log(value); return 'next is catch'; }).catch(function(e){ console.log(e); }).then(function(value){ console.log('execute,but value is ' + value); }); </script> </body> </html>
執行上述程式碼,chrome截圖如下:
好了,到這裡,我們已經瞭解了最常用的Promise.prototype.then和Promise.prototype.catch這兩個原型方法。另外,像Promise建構函式還有屬於自身的方法,如all、rece、resolve、reject等,詳情請點選這裡(here)。
通過一路上對Promise的講述,我們也有了一定的認識,其實Promise並沒有想象中的那麼難以理解嘛。懂得Promise概念後,其實我們自己也可以實現一個簡易版的Promise。下面就一同嘗試實現一個唄。
四、模擬Promise
假設:有三個非同步操作方法f1,f2,f3,且f2依賴於f1,f3依賴於f2。如果,我們採用ES6中Promise鏈式呼叫的思想,我們可以將程式編寫成這樣:
f1().then(f2).then(f3);
那麼,通過上面這一系列鏈式呼叫,怎樣才能達到與ES6中Promise相似的功能呢?
初步想法:首先將上述鏈式呼叫的f2、f3儲存到f1中,當f1中的非同步執行完後,再呼叫執行f2,並將f1中的f3儲存到f2中,最後,等f2中的非同步執行完畢後,呼叫執行f3。詳細構思圖,如下:
從上圖可知,由於f1、f2 、f3是可變得,所以儲存陣列佇列thens,可放入,我們即將建立的模擬Promise建構函式中。具體實現程式碼如下:
//模擬Promise function Promise(){ this.thens = []; }; Promise.prototype = { constructor: Promise, then: function(callback){ this.thens.push(callback); return this; } };
並且,需要一個Promise.prototype.resolve原型方法,來實現:當f1非同步執行完後,執行緊接著f1後then中的f2方法,並將後續then中方法,嫁接到f2中,如f3。具體實現程式碼如下:
//模擬Promise,增加resolve原型方法 function Promise(){ this.thens = []; }; Promise.prototype = { constructor: Promise, then: function(callback){ this.thens.push(callback); return this; }, resolve: function(){ var t = this.thens.shift(), p; if(t){ p = t.apply(null,arguments); if(p instanceof Promise){ p.thens = this.thens; } } } };
測試程式碼(程式碼太長,自行開啟並執行)。
function f1() { var promise = new Promise(); setTimeout(function () { console.log(1); promise.resolve(); }, 1500) return promise; } function f2() { var promise = new Promise(); setTimeout(function () { console.log(2); promise.resolve(); }, 1500); return promise; } function f3() { var promise = new Promise(); setTimeout(function () { console.log(3); promise.resolve(); }, 1500) return promise; } f1().then(f2).then(f3);
仔細品味,上述實現的Promise.prototype.resolve方法還不夠完美,因為它只能夠滿足於f1、f2、f3等方法都是使用模擬的Promise非同步執行的情況。而,當其中有不是返回的Promise物件的呢,而是返回一個數字,亦或是什麼也不返回,該怎麼辦?所以,針對以上提出的種種可能,再次改進resolve。改善程式碼如下:
//模擬Promise,改善resolve原型方法 var Promise = function () { this.thens = []; }; Promise.prototype = { constructor: Promise, then: function(callback){ this.thens.push(callback); return this; }, resolve: function () { var t,p; t = this.thens.shift(); t && (p = t.apply(null, arguments)); while(t && !(p instanceof Promise)){ t = this.thens.shift(); t && (p = t.call(null, p)); } if(this.thens.length){ p.thens = this.thens; }; } }
測試程式碼(程式碼太長,自行開啟並執行)。
function f1() { var promise = new Promise(); setTimeout(function () { console.log(1); promise.resolve(); }, 1500) return promise; } function f2() { var promise = new Promise(); setTimeout(function () { console.log(2); promise.resolve(); }, 1500); return promise; } function f3() { var promise = new Promise(); setTimeout(function () { console.log(3); promise.resolve(); }, 1500) return promise; } function f4() { console.log(4); return 11; } function f5(x) { console.log(x+1); } function f6() { var promise = new Promise(); setTimeout(function () { console.log(6); promise.resolve(); }, 1500) return promise; } function f7() { console.log(7); } var that = f1().then(f2).then(f3).then(f4).then(f5).then(f6).then(f7);
好了,初步模擬的Promise就OK啦。
吼吼,對於Promise,我們這一路走來,發現原來也不過如此呢。
相關文章
- 細說 async/await 相較於 Promise 的優勢AIPromise
- [Javascript] Promise ES6 詳細介紹JavaScriptPromise
- 聊一聊Javascript中的Promise物件JavaScriptPromise物件
- 說說JavaScript中的事件模型JavaScript事件模型
- Javascript — PromiseJavaScriptPromise
- Promise in JavascriptPromiseJavaScript
- JavaScript Promise 的使用技巧JavaScriptPromise
- JavaScript 裡的 Promise ChainingJavaScriptPromiseAI
- javascript中promise有什麼侷限JavaScriptPromise
- Javascript Promise用法JavaScriptPromise
- JavaScript Promise物件JavaScriptPromise物件
- JavaScript Promise 物件JavaScriptPromise物件
- JavaScript 在 Promise.then 方法裡返回新的 PromiseJavaScriptPromise
- 從Promise來看JavaScript中的Event Loop、Tasks和MicrotasksPromiseJavaScriptOOP
- 'return await promise' 與 'return promise' 這細微的區別AIPromise
- JavaScript Promise(基礎)JavaScriptPromise
- JavaScript Promise 詳解JavaScriptPromise
- 前端效能優化:細說JavaScript的載入與執行前端優化JavaScript
- JavaScript中不得不說的斷言?JavaScript
- Javascript基礎之-PromiseJavaScriptPromise
- JavaScript之淺析PromiseJavaScriptPromise
- [JavaScript] Promise 與 Ajax/AxiosJavaScriptPromiseiOS
- javascript非同步與promiseJavaScript非同步Promise
- javascript 進階之 - PromiseJavaScriptPromise
- [Javascript] Promise question with async awaitJavaScriptPromiseAI
- JavaScript 中 forEach、map、filter 詳細JavaScriptFilter
- 現代JavaScript — ES6+中的Imports,Exports,Let,Const和PromiseJavaScriptImportExportPromise
- 現代JavaScript:ES6+ 中的 Imports,Exports,Let,Const 和 PromiseJavaScriptImportExportPromise
- 透過面試題來說說Promise面試題Promise
- 從JavaScript中的類陣列物件說起JavaScript陣列物件
- JS 中的 PromiseJSPromise
- iOS中的PromiseiOSPromise
- 深入理解Javascript之PromiseJavaScriptPromise
- Salesforce Javascript(一) Promise 淺談SalesforceJavaScriptPromise
- JavaScript Promise由淺入深JavaScriptPromise
- JavaScript Promise查缺補漏JavaScriptPromise
- 說說JavaScript的型別轉換JavaScript型別
- 手寫 Promise 詳細解讀Promise
- 史上最最最詳細的手寫Promise教程Promise