手寫 Promise 類

參考文章:https://juejin.cn/post/6850037281206566919#heading-4

基礎(chǔ)版 Promise

const p1 = new Promise((resolve, reject) => {
  console.log('create a promise');
  resolve('成功了');
})

console.log("after new promise");

const p2 = p1.then(data => {
  console.log(data)
  throw new Error('失敗了')
})

const p3 = p2.then(data => {
  console.log('success', data)
}, err => {
  console.log('faild', err)
})

// 三個(gè)狀態(tài):PENDING抚官、FULFILLED李滴、REJECTED
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

class Promise {
  constructor(executor) {
    // 默認(rèn)狀態(tài)為 PENDING
    this.status = PENDING;
    // 存放成功狀態(tài)的值,默認(rèn)為 undefined
    this.value = undefined;
    // 存放失敗狀態(tài)的值,默認(rèn)為 undefined
    this.reason = undefined;

    // 調(diào)用此方法就是成功
    let resolve = (value) => {
      // 狀態(tài)為 PENDING 時(shí)才可以更新狀態(tài)致燥,防止 executor 中調(diào)用了兩次 resovle/reject 方法
      if(this.status ===  PENDING) {
        this.status = FULFILLED;
        this.value = value;
      }
    } 

    // 調(diào)用此方法就是失敗
    let reject = (reason) => {
      // 狀態(tài)為 PENDING 時(shí)才可以更新狀態(tài)蹭沛,防止 executor 中調(diào)用了兩次 resovle/reject 方法
      if(this.status ===  PENDING) {
        this.status = REJECTED;
        this.reason = reason;
      }
    }

    try {
      // 立即執(zhí)行,將 resolve 和 reject 函數(shù)傳給使用者  
      executor(resolve,reject)
    } catch (error) {
      // 發(fā)生異常時(shí)執(zhí)行失敗邏輯
      reject(error)
    }
  }

  // 包含一個(gè) then 方法送淆,并接收兩個(gè)參數(shù) onFulfilled税产、onRejected
  then(onFulfilled, onRejected) {
    if (this.status === FULFILLED) {
      onFulfilled(this.value)
    }

    if (this.status === REJECTED) {
      onRejected(this.reason)
    }
  }
}

寫完代碼我們可以測試一下:

const promise = new Promise((resolve, reject) => {
  resolve('成功');
}).then(
  (data) => {
    console.log('success', data)
  },
  (err) => {
    console.log('faild', err)
  }
)

控制臺輸出:

"success"
"成功"

異步操作

const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

class Promise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;
    // 存放成功的回調(diào)
    this.onResolvedCallbacks = [];
    // 存放失敗的回調(diào)
    this.onRejectedCallbacks= [];

    let resolve = (value) => {
      if(this.status ===  PENDING) {
        this.status = FULFILLED;
        this.value = value;
        // 依次將對應(yīng)的函數(shù)執(zhí)行
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    } 

    let reject = (reason) => {
      if(this.status ===  PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        // 依次將對應(yīng)的函數(shù)執(zhí)行
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    }

    try {
      executor(resolve,reject)
    } catch (error) {
      reject(error)
    }
  }

  then(onFulfilled, onRejected) {
    if (this.status === FULFILLED) {
      onFulfilled(this.value)
    }

    if (this.status === REJECTED) {
      onRejected(this.reason)
    }

    if (this.status === PENDING) {
      // 如果promise的狀態(tài)是 pending,需要將 onFulfilled 和 onRejected 函數(shù)存放起來偷崩,等待狀態(tài)確定后辟拷,再依次將對應(yīng)的函數(shù)執(zhí)行
      this.onResolvedCallbacks.push(() => {
        onFulfilled(this.value)
      });

      // 如果promise的狀態(tài)是 pending,需要將 onFulfilled 和 onRejected 函數(shù)存放起來阐斜,等待狀態(tài)確定后梧兼,再依次將對應(yīng)的函數(shù)執(zhí)行
      this.onRejectedCallbacks.push(()=> {
        onRejected(this.reason);
      })
    }
  }
}

測試一下:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('成功');
  },1000);
}).then(
  (data) => {
    console.log('success', data)
  },
  (err) => {
    console.log('faild', err)
  }
)

控制臺等待 1s 后輸出:

"success 成功"

ok!大功告成智听,異步問題已經(jīng)解決了羽杰!

熟悉設(shè)計(jì)模式的同學(xué),應(yīng)該意識到了這其實(shí)是一個(gè)發(fā)布訂閱模式到推,這種收集依賴 -> 觸發(fā)通知 -> 取出依賴執(zhí)行的方式考赛,被廣泛運(yùn)用于發(fā)布訂閱模式的實(shí)現(xiàn)。

then 的鏈?zhǔn)秸{(diào)用&值穿透特性

我們都知道莉测,promise 的優(yōu)勢在于可以鏈?zhǔn)秸{(diào)用颜骤。在我們使用 Promise 的時(shí)候,當(dāng) then 函數(shù)中 return 了一個(gè)值捣卤,不管是什么值忍抽,我們都能在下一個(gè) then 中獲取到,這就是所謂的then 的鏈?zhǔn)秸{(diào)用董朝。而且鸠项,當(dāng)我們不在 then 中放入?yún)?shù),例:promise.then().then()子姜,那么其后面的 then 依舊可以得到之前 then 返回的值祟绊,這就是所謂的值的穿透。那具體如何實(shí)現(xiàn)呢哥捕?簡單思考一下牧抽,如果每次調(diào)用 then 的時(shí)候,我們都重新創(chuàng)建一個(gè) promise 對象遥赚,并把上一個(gè) then 的返回結(jié)果傳給這個(gè)新的 promise 的 then 方法扬舒,不就可以一直 then 下去了么?那我們來試著實(shí)現(xiàn)一下凫佛。

const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

const resolvePromise = (promise2, x, resolve, reject) => {
  // 自己等待自己完成是錯(cuò)誤的實(shí)現(xiàn)讲坎,用一個(gè)類型錯(cuò)誤泽腮,結(jié)束掉 promise  Promise/A+ 2.3.1
  if (promise2 === x) { 
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }
  // Promise/A+ 2.3.3.3.3 只能調(diào)用一次
  let called;
  // 后續(xù)的條件要嚴(yán)格判斷 保證代碼能和別的庫一起使用
  if ((typeof x === 'object' && x != null) || typeof x === 'function') { 
    try {
      // 為了判斷 resolve 過的就不用再 reject 了(比如 reject 和 resolve 同時(shí)調(diào)用的時(shí)候)  Promise/A+ 2.3.3.1
      let then = x.then;
      if (typeof then === 'function') { 
        // 不要寫成 x.then,直接 then.call 就可以了 因?yàn)?x.then 會再次取值衣赶,Object.defineProperty  Promise/A+ 2.3.3.3
        then.call(x, y => { // 根據(jù) promise 的狀態(tài)決定是成功還是失敗
          if (called) return;
          called = true;
          // 遞歸解析的過程(因?yàn)榭赡?promise 中還有 promise) Promise/A+ 2.3.3.3.1
          resolvePromise(promise2, y, resolve, reject); 
        }, r => {
          // 只要失敗就失敗 Promise/A+ 2.3.3.3.2
          if (called) return;
          called = true;
          reject(r);
        });
      } else {
        // 如果 x.then 是個(gè)普通值就直接返回 resolve 作為結(jié)果  Promise/A+ 2.3.3.4
        resolve(x);
      }
    } catch (e) {
      // Promise/A+ 2.3.3.2
      if (called) return;
      called = true;
      reject(e)
    }
  } else {
    // 如果 x 是個(gè)普通值就直接返回 resolve 作為結(jié)果  Promise/A+ 2.3.4  
    resolve(x)
  }
}

class Promise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks= [];

    let resolve = (value) => {
      if(this.status ===  PENDING) {
        this.status = FULFILLED;
        this.value = value;
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    } 

    let reject = (reason) => {
      if(this.status ===  PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    }

    try {
      executor(resolve,reject)
    } catch (error) {
      reject(error)
    }
  }

  then(onFulfilled, onRejected) {
    //解決 onFufilled诊赊,onRejected 沒有傳值的問題
    //Promise/A+ 2.2.1 / Promise/A+ 2.2.5 / Promise/A+ 2.2.7.3 / Promise/A+ 2.2.7.4
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
    //因?yàn)殄e(cuò)誤的值要讓后面訪問到,所以這里也要跑出個(gè)錯(cuò)誤府瞄,不然會在之后 then 的 resolve 中捕獲
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
    // 每次調(diào)用 then 都返回一個(gè)新的 promise  Promise/A+ 2.2.7
    let promise2 = new Promise((resolve, reject) => {
      if (this.status === FULFILLED) {
        //Promise/A+ 2.2.2
        //Promise/A+ 2.2.4 --- setTimeout
        setTimeout(() => {
          try {
            //Promise/A+ 2.2.7.1
            let x = onFulfilled(this.value);
            // x可能是一個(gè)proimise
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            //Promise/A+ 2.2.7.2
            reject(e)
          }
        }, 0);
      }

      if (this.status === REJECTED) {
        //Promise/A+ 2.2.3
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e)
          }
        }, 0);
      }

      if (this.status === PENDING) {
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e)
            }
          }, 0);
        });

        this.onRejectedCallbacks.push(()=> {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0);
        });
      }
    });
  
    return promise2;
  }
}

測試一下:

const promise = new Promise((resolve, reject) => {
  reject('失敗');
}).then().then().then(data=>{
  console.log(data);
},err=>{
  console.log('err',err);
})

控制臺輸出:

"失敗 err"

至此碧磅,我們已經(jīng)完成了 promise 最關(guān)鍵的部分:then 的鏈?zhǔn)秸{(diào)用和值的穿透。搞清楚了 then 的鏈?zhǔn)秸{(diào)用和值的穿透遵馆,你也就搞清楚了 Promise鲸郊。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市货邓,隨后出現(xiàn)的幾起案子秆撮,更是在濱河造成了極大的恐慌,老刑警劉巖换况,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件职辨,死亡現(xiàn)場離奇詭異,居然都是意外死亡戈二,警方通過查閱死者的電腦和手機(jī)舒裤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來觉吭,“玉大人腾供,你說我怎么就攤上這事∠侍玻” “怎么了伴鳖?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長徙硅。 經(jīng)常有香客問我榜聂,道長,這世上最難降的妖魔是什么闷游? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任峻汉,我火速辦了婚禮,結(jié)果婚禮上脐往,老公的妹妹穿的比我還像新娘。我一直安慰自己扳埂,他們只是感情好业簿,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著阳懂,像睡著了一般梅尤。 火紅的嫁衣襯著肌膚如雪柜思。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天巷燥,我揣著相機(jī)與錄音赡盘,去河邊找鬼。 笑死缰揪,一個(gè)胖子當(dāng)著我的面吹牛陨享,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播钝腺,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼抛姑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了艳狐?” 一聲冷哼從身側(cè)響起定硝,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎毫目,沒想到半個(gè)月后蔬啡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡镀虐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年星爪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片粉私。...
    茶點(diǎn)故事閱讀 40,561評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡顽腾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出诺核,到底是詐尸還是另有隱情抄肖,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布窖杀,位于F島的核電站漓摩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏入客。R本人自食惡果不足惜管毙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望桌硫。 院中可真熱鬧夭咬,春花似錦、人聲如沸铆隘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽膀钠。三九已至掏湾,卻和暖如春裹虫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背融击。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工筑公, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人尊浪。 一個(gè)月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓匣屡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親际长。 傳聞我的和親對象是個(gè)殘疾皇子耸采,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容