「前端客棧?想必前方就是寧安城了,此地爲寧安城的郊區,」書生忖道,「時間不早了,今日不如在此停頓休整一日。」前端
進得店裏,書生吩咐店小二將馬匹安排穩當。訂罷客房,要了杯茶,便進房間休息。不到半個鐘頭,外面天色已黑,便下來吃晚飯。node
入住的人並很少,十來張桌子都有些空曠。店小二靠牆邊站着,大概其餘客人下來得早。es6
書生找了靠角落的位置坐下,店小二隨即跟來,遞上食單。「客官您看看想吃什麼,這上面除了排骨和鵝,其餘的都有。」設計模式
書生望向店小二,內心暗道:「這店小二雖是敝巾舊服,但生得腰圓背厚,面闊口方,更兼劍眉星眼,料想不會是久困之人。」api
「一份小白菜,蔥拌豆腐,三兩牛肉,一碗米飯,加一瓶楊梅汁。」promise
「好叻,您稍等片刻。」瀏覽器
書生點點頭,隨即掏出手機打開這個禮拜的JavaScript weekly,想看看有何新鮮。異步
無精彩之處。函數
不到一刻鐘,店小二端來托盤,所有上齊。隨即待在書生身後的牆邊。網站
書生左手劃手機,右手夾着小蔥拌豆腐,不時喝一口楊梅汁,腦子裏胡思亂想。想到今天一成天不曾好好說話,心生一種孤寂之感。想到前面的路彷佛還很漫長,家鄉還有人等着,書生嘆了口氣,收起了手機,斷斷續續吃着。
想說說話。
見店小二目不轉睛,近似呆滯,書生便擡手示意,招來店小二。
「大家店爲啥叫前端客棧呢?」
「客官,這店名啊,小的據說掌櫃的之前是作前端的,後來轉行了,作過許多行當,但最難忘的仍是前端的日子,店名也跟着叫了前端客棧。說來也怪,我來這不久也漸漸對前端生了興趣,目前本身也在學。只是基礎不太行,學得至關吃力。」店小二說完很差意思地笑了笑。
「這倒有趣,我還覺得是由於這地離寧安城不遠。我對前端也略知一二,不知道你學得如何了?」
「吃力歸吃力,但學得還算有頭有尾。最近開始學es6的Promise了,進展有些慢,多是小的比較笨吧。」
「Promise我也略知,不知你有何困惑之處?」
「小的還沒弄清Promise要解決什麼問題,只是鏈式調用嗎?」
「在Promise出現以前,瀏覽器中的回調就已經很常見了。一個回調還好,但若是回調函數嵌套多了,則容易出現被稱爲‘回調地獄’的狀況。這你知道,對吧?」
店小二點點頭,「回調地獄就是回調函數嵌套太多,若是邏輯的分支太多會讓函數難以理解,調用順序和代碼順序對應不上。慢慢地可能難以理解難以維護。」
「是的。爲了緩解回調地獄的問題,你們作了很多嘗試,好比儘可能恰當地拆分邏輯,而後用函數表示每個步驟。
doSomething(function(result) { doSomethingElse(result, function(newResult) { doThirdThing(newResult, function(finalResult) { console.log('Got the final result: ' + finalResult); }, failureCallback); }, failureCallback); }, failureCallback); // 得益於函數變量提高,能夠自由組織位置。 function doSomething (cb){ ... cb && cb(value) } function doSomethingElse (input,cb){ ... cb && cb(value) } function doThirdThing (input,cb){ ... cb && cb(value) }
這樣邏輯上可看得清楚一些。
「是的。除此以外,回掉函數的錯誤處理也是一個問題。因爲回調函數不可控,咱們編碼時須要主動假設回調可能出錯,編碼時也要有相應處理。nodejs的風格是將回調函數返回的第一個參數做爲錯誤。但這意味着須要處理每個錯誤,流程將很是繁瑣,須要判斷不少分支。
此外,在某些狀況中,人們可能使用發佈訂閱模式對代碼邏輯進行處理。」
「發佈訂閱模式也被稱爲觀察者模式嗎?」店小二問道。
書生微微點頭:「《headfirst設計模式》提到發佈訂閱模式只是觀察者模式的別名,但如今人們每每認爲有些區別。觀察者模式的前提是被觀察者有本身的發佈機制,是一對多;而發佈訂閱模式中,發佈者和訂閱者都依賴一箇中間人,全交由中間人處理,能夠處理多對多。不過大致上它們是同樣的,都是一種響應機制。」
「小的似懂非懂。」
但看到店小二的眼神仍舊不太明朗,書生打算慢慢地講。
「除此以外,回調函數的執行沒有一個統一的規範。當你想在某個函數中執行一個回調函數,那麼就必須以來一個前提:這個函數定義好了形參,而且會在函數中調用。
乍看這並非個問題,只是要定義好就行。可是否有彷佛成了一種約定,在代碼層面無法檢測。若是有了統一的規範,統一的檢測方式,事情是否是會變得更簡單呢?」說完,書生定定地看着店小二。
「Promise就是那個規範,對吧?」
「是的,另外Promise還讓異步調用變得更加可控。它不僅是檢測入參是否有回調而且調用,它同時能夠傳遞這個回調到合適的時機去處理,這個是它最厲害的。以下面代碼,cb在什麼時候執行,就看resolveCallback要怎麼處理。」
const supportCallback = function(cb){ const resolveCallback() = ... resolveCallback(cb) }
「小的聽不太懂了,小的只知道Promise調用和那幾個api。」
「那你給我講講怎麼使用,如何?我看看你的瞭解多少。」
「好的,讓小的直接敲代碼吧。」說完從裏屋拿出一臺電腦,坐在書生旁邊敲了起來。其餘客人仍舊擺着原來的姿式,似遊戲裏npc通常,未曾打擾這兩人。
//店小二在註釋中寫道:「如下是最基本的建立。「 const dianxiaoerPromise = new Promise(resolve => { setTimeout(() => { resolve('hello') }, 2000) }) //建立以後用then dianxiaoerPromise.then(res => { //1then console.log(res) }) //能夠分開的屢次then,then裏面的函數會按註冊順序執行 dianxiaoerPromise.then(res => { //2then console.log(res) }) //還能夠鏈式then, 鏈式then中傳遞各類類型數據,主要分爲PromiseLike和非PromiseLike //如下爲非PromiseLike dianxiaoerPromise.then(res => { console.log(val) // 參數val = 'hello' return 'world' }).then(val => { console.log(val) // 參數val = 'world' }) //PromiseLike //若是有異步操做,則把操做放在Promise中,返回出去,外層的then則會在異步操做以後執行 dianxiaoerPromise.then(res => { return new Promise(resolve => { setTimeout(() => { resolve('world') }, 2000) }) }).then(val => { console.log(val) // 參數val = 'world' }) //裏面返回的裏層的promise也能夠then,而且順序可控 dianxiaoerPromise.then(res => { return new Promise(resolve => { setTimeout(() => { resolve('world') }, 2000) }).then(res=>{ return '調皮' }) }).then(val => { console.log(val) // 參數val = '調皮' }) // 但不能像下面這樣,返回以前建立的Promise;這是個不合法的操做。 // 若是瀏覽器支持這麼運行,後面的then永遠沒法執行 dianxiaoerPromise.then(res => { return dianxiaoerPromise }).then(val => { console.log(val) // 參數val = 'world' })
書生看着店小二敲代碼,不時嗯兩聲,表示承認,同時細細地吃牛肉,喝楊梅汁。偶爾會一口吃上幾種食物,白菜、豆腐和牛肉加酸梅汁,混在一塊兒。
敲到這裏,書生吃了6口雜燴,每一口都嚼了二十多下。
「不錯不錯,基本用法你掌握得至關準確了。你看你上面的寫法已經緩解了回調地獄問題,異步也變得更更加可控。這就是我以前說的resolveCallback的功能:若是傳遞過來的還在執行的異步操做,則將本身的回調轉交給那個異步操做去觸發。」
店小二點點頭。
「那麼接下來你寫寫Promise中的錯誤處理吧。」書生又道。
店小二輕輕應了一聲,繼續敲代碼。
//Promise中的錯誤處理主要有兩種方式 //用onReject函數 const p1 = new Promise(function(resolve, reject) { resolve('Success'); }); p1.then(function(value) { console.log(value); // "Success!" throw 'oh, no!'; }).then(function(res){ console.log('不會執行'); }, function (err) { //onReject函數 console.error(err); //'oh, no!'; }); // 或者用catch,catch是在內部調用then(undefined, onRejected)) p1.then(function(value) { console.log(value); // "Success!" throw 'oh, no!'; }).catch(function (err) { console.error(err); //'oh, no!'; }); // 在異步函數中拋出的錯誤不會被catch捕獲到 const p2 = new Promise(function(resolve, reject) { setTimeout(function() { throw 'Uncaught Exception!'; }, 1000); }); p2.catch(function(e) { console.log(e); // 不會執行 }); //錯誤處理會根據promise的狀態處理, //若是已經fullfilled,已經完成 Promise.resolve("calling next").catch(function (reason) { //這個方法永遠不會調用 console.log(reason); }); //若是是失敗狀態 Promise.reject("calling next").catch(function (reason) { //則會和then同樣放在微觀任務(瀏覽器執行機制)中,適時執行 console.log(reason); }); //只要拋出的錯誤或reject在某個環節catch住,後面的流程則迴歸正常 Promise.reject("calling next").catch(function (reason) { //則會和then同樣放在微觀任務(瀏覽器執行機制)中,適時執行 console.error(reason); return '錯誤已處理' }).then(res=>{ //執行 console.log(res) //'錯誤已處理' },err=>{ //後面的錯誤處理不會執行 }) //若是catch中出現拋出錯誤,則catch後面的catch纔會起做用 Promise.reject("calling next").catch(function (reason) { throw "error" }).then(res=>{ //不會執行 console.log(res) // },err=>{ //這裏纔會執行 //用cacht也能執行 console.error(err) }) //若是promise的reject不處理,則瀏覽器會觸發一個unhandledrejection,通常觸發源在window,也能夠是Worker //這時候進行事件監聽 window.addEventListener("unhandledrejection", (event) => { // 可能要在開發服務下才能獲取到事件,直接打開文件獲取不到 console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`); });
「甚奇,當朝已出現電腦和手機這等精巧之物,爲什麼老百姓仍依仗着馬匹遠行,」書生緩緩思索着。店小二兩手一攤,舒展了下腰身。
房裏穿過一陣風,帶着房檐上的燈籠微微晃動。風聲,蟋蟀叫,幾位客人在小聲說着什麼。「此刻愜意便可,」書生腦中響起這句話。
「小的已經寫完Promise的錯誤處理。」店小二把電腦稍稍移動,轉向書生。
書生看了幾眼:「很是不錯,你對promise的掌握已經至關熟練。那麼你可知道Promise的那些api?繼續寫吧。」
店小二寫了起來。
白菜和小蔥拌豆腐已吃大半,牛肉還剩很多,書生認真地看着店小二敲代碼。
//Promise.all 的基本調用 //能夠傳入promise實例和非promise,非promise會被保留 var p1 = Promise.resolve(3); var p2 = 1337; var p3 = new Promise((resolve, reject) => { setTimeout(resolve, 100, 'foo'); }); Promise.all([p1, p2, p3]).then(values => { console.log(values); // [3, 1337, "foo"] }); //當all中傳入的可迭代對象爲空時,all爲同步 console.log("同步1"); const p1 = Promise.all([]); console.log(p1); //resolved console.log("異步"); const p2 = Promise.all([1, 3, 4, 5]); console.log(p2); //pending //若是在全部都完成前有個失敗的,則all狀態爲失敗,而且返回失敗值 var p1 = new Promise((resolve, reject) => { setTimeout(resolve, 1000, 'one'); }); var p2 = new Promise((resolve, reject) => { reject('reject'); }); Promise.all([p1, p2, ]).then(values => { },error=>{ console.error(error) // reject }); //Promise.race正常調用 const promise1 = new Promise((resolve, reject) => { setTimeout(resolve, 500, 'one'); }); const promise2 = new Promise((resolve, reject) => { setTimeout(resolve, 100, 'two'); }); Promise.race([promise1, promise2]).then((value) => { console.log(value); // Both resolve, but promise2 is faster }); //若是爲race傳入空的可迭代對象,則實例一直pending const p = Promise.race([]); console.log(p); //pending setTimeout(() => { console.log(p); //pending }, 0); //設x爲race的參數中所能找到的第一個非pending狀態promise的值,若是存在x,則返回x //若是不存在x,則返回第一個非pending的promise的返回值 const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve(1); }, 500); }); const p2 = Promise.resovle(2); const race = Promise.race([p1, p2,3]).then((res) => { console.log(res); //2 }); const race = Promise.race([p1, 3, p2]).then((res) => { console.log(res); //3 }); //Promise.resolve正常調用 Promise.resolve("Success").then(function(value) { console.log(value); // "Success" }, function(value) { // 不會被調用 }); //resolve一個promise, //值得一提的時,當值爲promise時,resolve返回的promise的狀態會跟隨傳入的promise //就是說,它的狀態不必定是resolved //而reject方法則必定是reject const p1 = new Promise(function (resolve, reject) { setTimeout(() => { resolve(15); }, 1000); // 或者reject('error') }); Promise.resolve(p1).then(function (value) { console.log("value: " + value); },e=>{ console.error(e) }); //resolve一個thenable對象也能夠調用對象中的then var thenable = { then: function (resolve) { resolve("Resolving"); }, }; Promise.resolve(thenable).then((res) => { console.log(res); }); //then中報錯也能處理 var thenable = { then: function (resolve) { throw new TypeError("Throwing"); }, }; Promise.resolve(thenable).catch((e) => { console.error(e); }); //reject正常調用, const p = Promise.reject(Promise.resolve()) console.log(p) //狀態永遠是rejected p.then(function() { // not called }, function(error) { console.error(error); // Stacktrace });
「小的已寫完,」店小二停下來講道。
書生的白菜和豆腐吃得差很少了,還剩下許多牛肉。從小便如此,喜歡的東西每每要留在後面。
「精彩,看得出來基礎很是紮實。那麼,你是打算深刻理解Promise?」書生夾着牛肉說道。
「正是。小的聽聞Promise並不僅是瀏覽器的原生對象,能夠經過js代碼來實現,但小的琢磨不透它該如何實現。嘗試看了Promise規範,卻於事無補,終究理解不能。」
「正好,當年我從西域的一個網站上找到一份Promise源碼,潛心修煉以後已大概融合其心法。源碼先傳授給你,你先本身過一遍,咱們再交流,如何?」
店小二心照不宣:「願先生賜教!」
「那好,你先看着吧,我先回房休息。」丟下源碼,吃完幾口牛肉,灌了楊梅汁,書生便回房休息了。
燈火比剛纔又暗了一些。店小二還坐在老位子,眉頭多半緊縮,偶爾舒放。
做爲一個店小二,他確實有些蹊蹺,但在故事以外他仍然是個一本正經的店小二。至於其餘客人,則都是尋常旅人,各個生得平平無奇,吃飯、休息,而後上路,沒有絲毫衝突值得訴諸筆墨。
class MyPromise { constructor(exector) { this.status = MyPromise.PENDING; // 1.3 「value」 is any legal JavaScript value (including undefined, a thenable, or a promise). this.value = null; // 1.5 「reason」 is a value that indicates why a promise was rejected. this.reason = null; this.resolved = false; /** * 2.2.6 then may be called multiple times on the same promise * 2.2.6.1 If/when promise is fulfilled, all respective onFulfilled callbacks must execute in the order of their originating calls to then * 2.2.6.2 If/when promise is rejected, all respective onRejected callbacks must execute in the order of their originating calls to then. */ this.onFulfilledCallback = []; this.onRejectedCallback = []; this.initBind(); this.init(exector); } initBind() { this.resolve = this.resolve.bind(this); this.reject = this.reject.bind(this); } init(exector) { try { exector(this.resolve, this.reject); } catch (err) { this.reject(err); } } resolve(value) { if (this.status === MyPromise.PENDING && this.resolved === false) { this.resolved = true; setTimeout(() => { this.status = MyPromise.FULFILLED; this.value = value; this.onFulfilledCallback.forEach((cb) => cb(this.value)); }); } } reject(reason) { if (this.status === MyPromise.PENDING) { setTimeout(() => { this.status = MyPromise.REJECTED; this.reason = reason; this.onRejectedCallback.forEach((cb) => cb(this.reason)); }); } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (value) => value; onRejected = typeof onRejected === "function" ? onRejected : (reason) => { throw reason; }; let promise2; if (this.status === MyPromise.FULFILLED) { return (promise2 = new MyPromise((resolve, reject) => { setTimeout(() => { try { const x = onFulfilled(this.value); MyPromise.resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }); })); } if (this.status === MyPromise.REJECTED) { return (promise2 = new MyPromise((resolve, reject) => { setTimeout(() => { try { const x = onRejected(this.reason); MyPromise.resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }); })); } if (this.status === MyPromise.PENDING) { return (promise2 = new MyPromise((resolve, reject) => { this.onFulfilledCallback.push((value) => { try { const x = onFulfilled(value); MyPromise.resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }); this.onRejectedCallback.push((reason) => { try { const x = onRejected(reason); MyPromise.resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }); })); } } } // 2.1 A promise must be in one of three states: pending, fulfilled, or rejected. MyPromise.PENDING = "pending"; MyPromise.FULFILLED = "fulfilled"; MyPromise.REJECTED = "rejected"; MyPromise.resolvePromise = (promise2, x, resolve, reject) => { let called = false; /** * 2.3.1 If promise and x refer to the same object, reject promise with a TypeError as the reason. */ if (promise2 === x) { const err = new TypeError( "cannot return the same promise object from onfulfilled or on rejected callback." ); console.error(err); return reject(err); } if (x instanceof MyPromise) { // 處理返回值是 Promise 對象的狀況 /** * new MyPromise(resolve => { * resolve("Success") * }).then(data => { * return new MyPromise(resolve => { * resolve("Success2") * }) * }) */ if (x.status === MyPromise.PENDING) { /** * 2.3.2.1 If x is pending, promise must remain pending until x is fulfilled or rejected. */ x.then( (y) => { MyPromise.resolvePromise(promise2, y, resolve, reject); }, (reason) => { reject(reason); } ); } else { /** * 2.3 If x is a thenable, it attempts to make promise adopt the state of x, * under the assumption that x behaves at least somewhat like a promise. * * 2.3.2 If x is a promise, adopt its state [3.4]: * 2.3.2.2 If/when x is fulfilled, fulfill promise with the same value. * 2.3.2.4 If/when x is rejected, reject promise with the same reason. */ x.then(resolve, reject); } /** * 2.3.3 Otherwise, if x is an object or function, */ } else if ((x !== null && typeof x === "object") || typeof x === "function") { /** * 2.3.3.1 Let then be x.then. * 2.3.3.2 If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason. */ try { // then 方法可能設置了訪問限制(setter),所以這裏進行了錯誤捕獲處理 const then = x.then; if (typeof then === "function") { /** * 2.3.3.2 If retrieving the property x.then results in a thrown exception e, * reject promise with e as the reason. */ /** * 2.3.3.3.1 If/when resolvePromise is called with a value y, run [[Resolve]](promise, y). * 2.3.3.3.2 If/when rejectPromise is called with a reason r, reject promise with r. */ then.call( x, (y) => { /** * If both resolvePromise and rejectPromise are called, * or multiple calls to the same argument are made, * the first call takes precedence, and any further calls are ignored. */ if (called) return; called = true; MyPromise.resolvePromise(promise2, y, resolve, reject); }, (r) => { if (called) return; called = true; reject(r); } ); } else { resolve(x); } } catch (e) { /** * 2.3.3.3.4 If calling then throws an exception e, * 2.3.3.3.4.1 If resolvePromise or rejectPromise have been called, ignore it. * 2.3.3.3.4.2 Otherwise, reject promise with e as the reason. */ if (called) return; called = true; reject(e); } } else { // If x is not an object or function, fulfill promise with x. resolve(x); } };