這裏是我自定義的Promise,若是想看原版,能夠跳過最後有符合PromiseA+規範的源碼jquery
class 承諾 { constructor(處理器函數) { //1. 處理器函數是在_new 承諾(處理器函數)_的時候當作參數傳入到構造函數裏的,構造函數裏會執行這個處理器函數. let self = this //2.註冊函數執行時可能沒有前綴,若是註冊函數裏用this可能就是window,這裏把this賦給變量,造成閉包. this.狀態 = '等待...' //3.這個屬性有三個值,默認是等待,只有調用註冊成功或註冊失敗纔會改變這個屬性的值,**只能改變一次** this.成功值 = undefined //4.這個成功值是由使用者調用註冊成功函數的時候傳進函數裏的,而後咱們在註冊成功函數給這個屬性賦值,簡單點說就是形參賦值給屬性 this.失敗緣由 = undefined //5.同上 //說明:這裏須要瞭解'那時()'函數,能夠看完'那時()'再來看,這兩在異步的狀況纔會用到,使用者在調用'那時()'的時候,承諾仍然是等待狀態(也就是說註冊成功和註冊失敗兩個函數沒有被調用) this.成功回調函數組 = [] //6.承諾變爲成功時要調用的函數,屢次調用then傳函數,咱們就把'那時()'傳進來的函數push到這個數組裏, this.失敗回調函數組 = [] //7.同上,承諾變爲失敗時要調用的函數 let 註冊成功 = function (成功值) { //7.'註冊成功()'函數當實參傳進了處理器函數,這樣使用者在編寫處理器函數得時候能夠根據狀況調用. if (self.狀態 === '等待...') { //8.這個判斷的做用:承諾的狀態只能由等待->成功或等待->失敗,並且只能改變一次.咱們默認初始態是等待,若是不是等待說明以前已經調用過'註冊成功或註冊失敗' self.成功值 = 成功值 //9.將使用者調用'註冊成功(成功值)'函數傳進來的值由對象屬性保存,這個值在調用'那時()'傳進來的函數時會用到 self.狀態 = '成功' //10.調用註冊成功,改變承諾狀態爲成功 for (let 函數 of self.成功回調函數組) { //11.調用'那時()'傳進來的函數 函數(成功值) } // this.成功回調函數組.forEach(函數=>函數()) } } let 註冊失敗 = function (失敗緣由) { //12. 同上 if (self.狀態 === '等待...') { self.失敗緣由 = 失敗緣由 self.狀態 = '失敗' for (let 函數 of self.失敗回調函數組) { 函數(失敗緣由) } // this.成功回調函數組.forEach(函數=>函數()) } } try { 處理器函數(註冊成功, 註冊失敗) //13.執行處理器函數,把咱們定義好的兩個函數傳進去,這樣使用者就能夠用這兩個函數來改變承諾的狀態和值 } catch (錯誤) { 註冊失敗(錯誤) //14.處理器函數是使用者編寫的,有可能報錯,出錯了咱們就調用註冊失敗()來改變這個承諾的狀態和值 } } 那時(成功的回調, 失敗的回調) { //15.對象的'那時()'方法,由使用者傳進來兩個函數參數,規定當前承諾對象爲成功時調用第一個,失敗調用第二個 let self = this 成功的回調 = typeof 成功的回調 === 'function' ? 成功的回調 : 成功值 => 成功值 失敗的回調 = typeof 失敗的回調 === 'function' ? 失敗的回調 : 失敗緣由 => { throw 失敗緣由 } //16.這兩個參數是可選參數,當他們不是函數時咱們給他默認值 let 新的承諾 = new 承諾((註冊成功, 註冊失敗) => { //17.返回一個新的承諾,這樣就能夠鏈式調用'那時()'了,這裏要注意新承諾的狀態和值由'那時()'傳進來的函數執行狀況決定 //18.判斷承諾的狀態,決定當即執行回調仍是將回調函數push到回調函數組裏等使用者調用註冊成功()在註冊成功()裏執行 if (self.狀態 === '等待...') { //19.若是是等待,顯然承諾的成功值或失敗值仍是undefined,因此咱們把他push到回調函數組裏,讓他在註冊成功()或註冊失敗()函數裏調用 self.成功回調函數組.push(() => { //20.這個函數要異步,由於咱們會在裏面用到新承諾,然而新承諾如今還沒被賦值,要完全理解這裏應該須要js執行機制知識,目前我尚未.... setTimeout(() => { try { let 回調的返回值 = 成功的回調(self.成功值) //21.調用使用者傳進來的函數獲得返回值,若是使用者沒有寫返回語句默認是返回undefined 善後處理(新的承諾, 回調的返回值, 註冊成功, 註冊失敗) //22.根據返回值決定咱們這個新承諾的值和狀態 這裏傳進去的是咱們新承諾的註冊成功和註冊失敗函數,咱們在裏面調用他們來改變咱們新承諾的狀態 } catch (錯誤) { 註冊失敗(錯誤) //23.若是執行使用者的函數出錯就把咱們新承諾的狀態和值改變 } }) }) self.失敗回調函數組.push(() => { //24.同上 setTimeout(() => { try { let 回調的返回值 = 失敗的回調(self.失敗緣由) 善後處理(新的承諾, 回調的返回值, 註冊成功, 註冊失敗) } catch (錯誤) { 註冊失敗(錯誤) } }) }) } if (self.狀態 === '成功') { //25.若是承諾的狀態是成功的說明註冊成功()函數已經被調用了,承諾的狀態和值都被使用者改變了, //咱們能夠取到對應的值來傳進回調裏,讓使用者用. setTimeout(() => { try { //邏輯同20-23 let 回調的返回值 = 成功的回調(self.成功值) 善後處理(新的承諾, 回調的返回值, 註冊成功, 註冊失敗) } catch (錯誤) { 註冊失敗(錯誤) } }) } if (self.狀態 === '失敗') { //同上 setTimeout(() => { try { let 回調的返回值 = 失敗的回調(self.失敗緣由) 善後處理(新的承諾, 回調的返回值, 註冊成功, 註冊失敗) } catch (錯誤) { 註冊失敗(錯誤) } }) } }) return 新的承諾 } }
//26.這個是核心,涉及到遞歸,這裏我寫的和PromiseA+規範的實現不一樣,他判斷的是thenable,兼容性好,我只是爲了理解Promise原理因此就簡化了. function 善後處理(新的承諾, 回調的返回值, 註冊成功, 註冊失敗) { // let p2 = p.那時((成功值)=>{ // return p2 // }) if (回調的返回值 instanceof 承諾) { //27.判斷使用者寫的回調函數的返回值是否是一個承諾,若是不是承諾就直接調用咱們新承諾的註冊成功()函數改變咱們新承諾的狀態和值 //若是返回值是一個承諾咱們就獲得這個承諾的值,把這個值給咱們新承諾的註冊函數 if (新的承諾 === 回調的返回值) { //28.這裏是爲了解決這種使用狀況 // let p2 = p.那時((成功值)=>{ // return p2 // }) 註冊失敗(new TypeError('循環引用')) return } try { 回調的返回值.那時((成功值) => { 善後處理(新的承諾, 成功值, 註冊成功, 註冊失敗) //29.若是使用者返回的承諾的值仍是一個承諾,繼續'那時()'直到不是承諾 //注意:這裏傳進去的註冊成功,註冊失敗是咱們新承諾的註冊函數,遞歸進去,當不是承諾時就改變咱們新承諾的狀態和值了,而後遞歸一層層返回 //這裏是進遞歸,其餘狀況就是出遞歸 }, (失敗緣由) => { 註冊失敗(失敗緣由) }) } catch (錯誤) { 註冊失敗(錯誤) } } else { 註冊成功(回調的返回值) } }
//測試一 p = new 承諾((註冊成功, 註冊失敗) => { setTimeout(()=>{ 註冊失敗('abc') },1000) }) p.那時((成功的值) => { console.log(成功的值) },(失敗緣由)=>{ console.log(失敗緣由) }) //輸出abc
//測試二:返回值是承諾 p = new 承諾((註冊成功, 註冊失敗) => { setTimeout(() => { 註冊成功('我是最外面的') }, 1000) }) p.那時((成功值) => { console.log(成功值) let p11 = new 承諾((註冊成功, 註冊失敗) => { let p22 = new 承諾((註冊成功, 註冊失敗) => { 註冊成功('最裏層') }) 註冊成功(p22) }) return p11 }, 1) .那時((成功值) => { console.log(成功值) }, (失敗緣由) => { console.log(失敗緣由) }) //輸出: //我是最外層 //我是最裏層
//測試三:失敗穿透 p = new 承諾((註冊成功, 註冊失敗) => { throw '(╥╯^╰╥)' setTimeout(() => { 註冊成功('♪(´▽`)') }, 1000) }) p.那時((成功的值) => { console.log(成功的值) },(失敗緣由)=>{ console.log('第一次失敗:'+失敗緣由) throw '(╥╯^╰╥))' }).那時( (成功的值) => { console.log(成功的值) } ).那時(null,(失敗緣由) => { console.log('失敗穿透'+失敗緣由) }) //輸出 //第一次失敗:(╥╯^╰╥) //失敗穿透(╥╯^╰╥))
function Promise(executor) { let self = this; self.value = undefined; // 成功的值 self.reason = undefined; // 失敗的值 self.status = 'pending'; // 目前promise的狀態pending self.onResolvedCallbacks = []; // 可能new Promise的時候會存在異步操做,把成功和失敗的回調保存起來 self.onRejectedCallbacks = []; function resolve(value) { // 把狀態更改成成功 if (self.status === 'pending') { // 只有在pending的狀態才能轉爲成功態 self.value = value; self.status = 'resolved'; self.onResolvedCallbacks.forEach(fn => fn()); // 把new Promise時異步操做,存在的成功回調保存起來 } } function reject(reason) { // 把狀態更改成失敗 if (self.status === 'pending') { // 只有在pending的狀態才能轉爲失敗態 self.reason = reason; self.status = 'rejected'; self.onRejectedCallbacks.forEach(fn => fn()); // 把new Promise時異步操做,存在的失敗回調保存起來 } } try { // 在new Promise的時候,當即執行的函數,稱爲執行器 executor(resolve, reject); } catch (e) { // 若是執行executor拋出錯誤,則會走失敗reject reject(e); } } // then調用的時候,都是屬於異步,是一個微任務 // 微任務會比宏任務先執行 // onFulfilled爲成功的回調,onRejected爲失敗的回調 Promise.prototype.then = function (onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val; onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err } let self = this; let promise2; // 上面講了,promise和jquery的區別,promise不能單純返回自身, // 而是每次都是返回一個新的promise,才能夠實現鏈式調用, // 由於同一個promise的pending resolve reject只能更改一次 promise2 = new Promise((resolve, reject) => { if (this.status === 'resolved') { // 爲何要加setTimeout? // 首先是promiseA+規範要求的 // 其次是你們寫的代碼,有的是同步,有的是異步 // 因此爲了更加統一,就使用爲setTimeout變爲異步了,保持一致性 setTimeout(() => { try { // 上面executor雖然使用try catch捕捉錯誤 // 可是在異步中,不必定可以捕捉,因此在這裏 // 用try catch捕捉 let x = onFulfilled(self.value); // 在then中,返回值多是一個promise,因此 // 須要resolvePromise對返回值進行判斷 resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0) } if (self.status === 'rejected') { setTimeout(() => { try { let x = onRejected(self.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0) } if (self.status === 'pending') { self.onResolvedCallbacks.push(() => { setTimeout(() => { try { let x = onFulfilled(self.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0) }); self.onRejectedCallbacks.push(() => { setTimeout(() => { try { let x = onRejected(self.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0) }); } }); return promise2 } function resolvePromise(promise2, x, resolve, reject) { // 3.從2中咱們能夠得出,本身不能等於本身 // 當promise2和x是同一個對象的時候,則走reject if (promise2 === x) { return reject(new TypeError('Chaining cycle detected for promise')) } // 4.由於then中的返回值能夠爲promise,當x爲對象或者函數,纔有可能返回的是promise let called if (x !== null && (typeof x === 'object' || typeof x === 'function')) { // 8.從第7步,能夠看出爲何會存在拋出異常的可能,因此使用try catch處理 try { // 6.由於當x爲promise的話,是存在then方法的 // 可是咱們取一個對象上的屬性,也有可能出現異常,咱們能夠看一下第7步 let then = x.then // 9.咱們爲何在這裏用call呢?解決了什麼問題呢?能夠看上面的第10步 // x可能仍是個promise,那麼就讓這個promise執行 // 可是仍是存在一個惡做劇的狀況,就是{then:{}} // 此時須要新增一個判斷then是否函數 if (typeof then === 'function') { then.call(x, (y) => { // y是返回promise後的成功結果 // 一開始咱們在這裏寫的是resolve(y),可是考慮到一點 // 這個y,有可能仍是一個promise, // 也就是說resolve(new Promise(...)) // 因此涉及到遞歸,咱們把resolve(y)改爲如下 // 12.限制既調resolve,也調reject if (called) return called = true resolvePromise(promise2, y, resolve, reject) // 這樣的話,代碼會一直遞歸,取到最後一層promise // 11.這裏有一種狀況,就是不能既調成功也調失敗,只能挑一次, // 可是咱們前面不是處理過這個狀況了嗎? // 理論上是這樣的,可是咱們前面也說了,resolvePromise這個函數 // 是全部promise通用的,也能夠是別人寫的promise,若是別人 // 的promise可能既會調resolve也會調reject,那麼就會出問題了,因此咱們接下來要 // 作一下限制,這個咱們寫在第12步 }, (err) => { // err是返回promise後的失敗結果 if (called) return called = true reject(err) }) } else { if (called) return; called = true; resolve(x) // 若是then不是函數的話,那麼則是普通對象,直接走resolve成功 } } catch (e) { // 當出現異常則直接走reject失敗 if (called) return called = true reject(e) } } else { // 5.x爲一個常量,則是走resolve成功 resolve(x) } } module.exports = Promise; Promise.defer = Promise.deferred = function () { let dfd = {}; dfd.promise = new Promise((resolve, reject) => { dfd.resolve = resolve; dfd.reject = reject; }); return dfd; } //執行命令promises-aplus-tests promise.js檢測是否符合promiseA+規範,先安裝包