Promise深度學(xué)習(xí)---我のPromise/A+實(shí)現(xiàn)

原文地址https://fancierpj0.github.io/iPromise/

目錄 (づ ̄ 3 ̄)づ=> So沒有一個目錄真的很惱火啄育。勘畔。

[TOC]

本文會對Promise規(guī)范進(jìn)行一個比較完整的實(shí)現(xiàn)检吆,目的是為了加深對Promise各個特性的理解從而更好的應(yīng)用蔬将。

[warning] 注意:本文依據(jù)Promises/A+規(guī)范進(jìn)行Promise的實(shí)現(xiàn)

1.Promise/A+ 術(shù)語

1.1. promise

promise是一個對象或則函數(shù)危融,它的表現(xiàn)是依據(jù)Promises/A+這篇規(guī)范說明來定義的挖胃。

1.1. promise is an object or function with a then method whose behavior conforms to this specification.

1.2. theable

thenable是一個定義了then方法的對象或則函數(shù)屋休。

thenable is an object or function that defines a then method.

1.3. value

value可以是任何合法的JS值坞古,甚至包括undefined、一個thenable劫樟、一個promise痪枫。

value is any legal JavaScript value (including undefined, a thenable, or a promise).

1.4. exception

exception是一個用throw語句拋出的值。

exception is a value that is thrown using the throw statement.

1.5. reason

reason是一個為什么promise會被拒絕的理由叠艳。

reason is a value that indicates why a promise was rejected.

Promise規(guī)范要求

判斷一個東東是不是Promise奶陈,有三項(xiàng)主要的特征可作為參考

  • Promise有三種狀態(tài) pendingfulfilled附较、rejected
  • Promise含有then方法
  • Promise含有Promise Resolution Procedure (promise的狀態(tài)轉(zhuǎn)換處理方法)吃粒。

2.1. Promise狀態(tài)

一個promise必須處于 pending 、fulfilled拒课、rejected 三種狀態(tài)中的其中一種

下面是一個promise最基本的使用demo徐勃,我們先有個印象。

  • 其中promise實(shí)例化的時候傳入了一個函數(shù)作為參數(shù)捕发,這個函數(shù)我們稱之為 executor 疏旨,它能告訴我們何時將promise狀態(tài)從pending轉(zhuǎn)化為其余兩態(tài)中的一態(tài)。
  • then 方法是實(shí)例化對象下的一個方法扎酷,它能傳入兩個參數(shù)檐涝,一般是兩個回調(diào)函數(shù),對應(yīng)fulfilled和rejected兩個狀態(tài),當(dāng)promise從pengding狀態(tài)轉(zhuǎn)化成其中一個狀態(tài)時就會觸發(fā)對應(yīng)的回調(diào)函數(shù)谁榜。
let p = new Promise((resolve,reject)=>{
  let x = Math.random();
  console.log(x);
  if (x > .5) {
    resolve('我是你許下的諾言的那個東東');
  } else {
    reject('我是你未能實(shí)現(xiàn)諾言的理由');
  }
});

p.then((value)=>{ //綁定成功時的回調(diào)函數(shù)
  console.log('fulfilled:',value); //fulfilled:我是你許下的諾言的那個東東
},(reason)=>{ //綁定失敗時的回調(diào)函數(shù)
  console.log('rejected:',reason); //rejected:我是你未能實(shí)現(xiàn)諾言的理由
});

2.1.1. pending狀態(tài)

當(dāng)Promise處于pending狀態(tài)時幅聘,它可能轉(zhuǎn)換為fulfilled或則rejected狀態(tài)。

When pending, a promise:may transition to either the fulfilled or rejected state.

2.1.2. fulfilled狀態(tài)

當(dāng)Promise處于fulfilled狀態(tài)時窃植,它不再能轉(zhuǎn)換為其它狀態(tài) 且 它必須有一個值帝蒿,這個值不能被更改。

When fulfilled, a promise:

  • must not transition to any other state.
  • must have a value, which must not change.

2.1.3 rejected狀態(tài)

當(dāng)promise處于rejected時巷怜,它不再能轉(zhuǎn)換為其它狀態(tài) 且 它必須有一個理由葛超,這個理由不能被更改。

When rejected, a promise:

  • must not transition to any other state.
  • must have a reason, which must not change.

[danger]注意: 當(dāng)promise處于fulfilled或則rejected時延塑,它都有一個值绣张,這個值不能被更改,但是可以像使用常量一樣在這個值下面掛載其它值关带。

Here, “must not change” means immutable identity (i.e. ===), but does not imply deep immutability.

2.1. Promise實(shí)現(xiàn)

請先回顧一下我們在說Promise狀態(tài)時候最初的那個demo
我們通過實(shí)例化Promise時傳入了一個參數(shù)侥涵,這個參數(shù)是一個執(zhí)行函數(shù)(executor),它能決定什么時候?qū)romise轉(zhuǎn)換成fulfilled什么時候轉(zhuǎn)換成rejected宋雏。

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

function Promise(executor){
  let self = this; //緩存下
  self.value = undefined; //用來存放value和reason,因?yàn)閜romise只會處于一種狀態(tài)故可只用一個變量來表示芜飘。
  self.status = PENDING; //將初始狀態(tài)設(shè)置為pending
  self.onFulfilledCallbacks = []; //用來存放所有成功的回調(diào)函數(shù)
  self.onRejectedCallbacks = []; //用來存放所有失敗的回調(diào)函數(shù)

  try{
    executor(resolve,reject); //調(diào)用執(zhí)行函數(shù),將resolve和reject方法作為參數(shù)傳入
  }catch (e){
    reject(e); //若執(zhí)行函數(shù)中存在異常直接用拋出的值來拒絕promise
  }
  //-----------------------------------------------------------------------------------------------------------
  function resolve(value){ //此方法會隨著executor傳入而傳入
    setTimeout(function(){
      if(self.status === PENDING){ //確保狀態(tài)只會改變一次
        self.status = FULFILLED; //改變狀態(tài)
        self.value = value; //賦予一個值
        self.onFulfilledCallbacks.forEach(cb => cb(self.value)); //2.2.2. //2.2.6.
      }
    })
  }
  function reject(reason){
    setTimeout(function(){
      if(self.status === PENDING){
        self.status = REJECTED;
        self.value = reason;
        self.onRejectedCallbacks.forEach(cb => cb(self.value));
      }
    })
  }
}

以上實(shí)現(xiàn)了2.1. 磨总,promise的三種狀態(tài)以及狀態(tài)之間的改變嗦明。

executor,形參舍败、實(shí)參招狸、作用域鏈

我們可以發(fā)現(xiàn)最終轉(zhuǎn)換狀態(tài)時通過Promise內(nèi)部的兩個方法resolve和reject,這個兩個方法是在什么時候傳入的呢邻薯?
一個函數(shù)的參數(shù)查找裙戏,是從調(diào)用這個函數(shù)時所處的作用域開始查找的。
new Promise傳入的executor厕诡,是參數(shù)也是對executor函數(shù)的定義累榜,此時executor的resolve和reject為形參
我們new Promise的時候灵嫌,會執(zhí)行構(gòu)造函數(shù)Promise內(nèi)的代碼壹罚,也就是在這時executor被執(zhí)行,而executor此時所處的作用域是在Promise構(gòu)造函數(shù)內(nèi)部寿羞,resolve和reject方法作為實(shí)參被傳入猖凛。

2.2. then方法

一個promise必須提供一個then方法來使用它將要或則說已經(jīng)被賦予的 value 或則 reason,一個promise的then方法接收兩個參數(shù)

promise.then(onFulfilled,onRejected)

2.2.1. then參數(shù)

then中的參數(shù)皆為可選參數(shù)绪穆,如果onFulfilled或則說onRejected不是一個函數(shù)辨泳,那么將會被忽略虱岂。

Both onFulfilled and onRejected are optional arguments:

  • If onFulfilled is not a function, it must be ignored.
  • If onRejected is not a function, it must be ignored.

2.2.2. 如果onFulfilled是一個函數(shù)

  • 如果onFulfilled是一個函數(shù),它必須在promise狀態(tài)轉(zhuǎn)換為fulfilled時候就被調(diào)用菠红,并且promise被賦予的value會成為這個函數(shù)(onFulfilled)的第一個參數(shù)第岖。
  • onFulfilled不能在promise狀態(tài)轉(zhuǎn)化為fulfilled前就調(diào)用
  • onFulfilled函數(shù)不能重復(fù)調(diào)用

原文規(guī)范詳見Promises/A+

2.2.3. 如果onRejected是一個函數(shù)

  • 如果onRejected是一個函數(shù),它必須在promise狀態(tài)轉(zhuǎn)換為rejected時候就被調(diào)用试溯,并且promise被賦予的reason會成為這個函數(shù)(onRejected)的第一個參數(shù)蔑滓。
  • onRejected不能在promise狀態(tài)轉(zhuǎn)化為rejected前就調(diào)用
  • onRejected函數(shù)不能重復(fù)調(diào)用

2.2.4. onFulfilled 或則 onRejected 必須在執(zhí)行棧 只存在 platform code 時才能被調(diào)用。

2.2.5. onFulfilled 和 onRejected 必須被當(dāng)做函數(shù)調(diào)用遇绞。

2.2.6. 同一個promise實(shí)例可以調(diào)用多次then

  • 當(dāng)一個promise轉(zhuǎn)化為fulfilled狀態(tài)键袱,所有onFulfilled callback會按照回調(diào)函數(shù)通過then添加時的順序而執(zhí)行。
  • 當(dāng)一個promise轉(zhuǎn)化為rejected狀態(tài)摹闽,所有onRejected callback會按照回調(diào)函數(shù)通過then添加時的順序而執(zhí)行杠纵。

:then在同一個promise實(shí)例下多次調(diào)用,意味著可以在同一個promise的同一種狀態(tài)下綁定多個不同的回調(diào)函數(shù)钩骇,而這些回調(diào)函數(shù)執(zhí)行的順序和它們被綁定時的順序相同。

2.2.7. then必會返回一個新的promise

promise2 = promise1.then(onFulfilled,onRejected);
  • 如果onFulfilled或onRejected回調(diào)函數(shù)中返回了一個值铝量,假定為x倘屹,那么調(diào)用一個 promise解析方法 [[Resolve]](promise2,x)
  • 如果onFulfilled或者onRejected拋出了一個 exception(異常) e , promise2 必須以這個e作為reason來拒絕promise慢叨,使其狀態(tài)改變?yōu)閞ejected纽匙。
  • 如果onFulfilled不是一個函數(shù)且 promise1 的狀態(tài)為fulfilled,promise2必須以 promise1 的值來fulfilled拍谐。
  • 如果onRejected不是一個函數(shù)且 promise1 的狀態(tài)為rejected烛缔,promise2必須以 promise1 的理由來rejected。

2.2. Promise實(shí)現(xiàn)

2.2.提的是一個then的實(shí)現(xiàn)規(guī)則轩拨,而then主要作用為promise綁定回調(diào)函數(shù)践瓷,當(dāng)promise轉(zhuǎn)換狀態(tài)時會自動調(diào)用對應(yīng)的回調(diào)函數(shù)。(對應(yīng)規(guī)范2.2.2-2.2.3)
其實(shí)就是發(fā)布訂閱模式啦

function Promise(){
    ...
    function resolve(value){ 
    setTimeout(function(){ //2.2.4.
      if(self.status === PENDING){ //2.2.2.3-2.2.2.4
        self.status = FULFILLED; 
        self.value = value; 
        self.onFulfilledCallbacks.forEach(cb => cb(self.value)); //2.2.6.
      }
    })
  }
  function reject(reason){
    setTimeout(function(){
      if(self.status === PENDING){ //2.2.3.3-2.2.3.4
        self.status = REJECTED;
        self.value = reason;
        self.onRejectedCallbacks.forEach(cb => cb(self.value));
      }
    })
  }
}
//---------------------------------------------------------------------------------------------------
Promise.prototype.then = function (onFulfilled, onRejected) { //2.2.1.
  //2.2.7.3-2.2.7.4 //2.2.5.
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
  onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason};

  let self = this,
    promise2; //2.2.7.0 //聲明要返回的promise2

  if(self.status === PENDING){
    //2.2.7.
    return promise2 = new Promise(function(resolve,reject){
      //存儲then方法綁定的回調(diào)函數(shù) //2.2.6.
      self.onFulfilledCallbacks.push((value)=>{
        try{
          let x = onFulfilled(value);
          resolvePromise(promise2,x,resolve,reject); //2.2.7.1
        }catch (e){
          reject(e); //2.2.7.2
        }
      });
      self.onRejectedCallbacks.push((reason)=>{
        try{
          let x= onRejected(reason);
          resolvePromise(promise2,x,resolve,reject);
        }catch (e){
          reject(e);
        }
      });
    });
  }
};

關(guān)于platform code

Here “platform code” means engine, environment, and promise implementation code. In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack. This can be implemented with either a “macro-task” mechanism such as setTimeout or setImmediate, or with a “micro-task” mechanism such as MutationObserver or process.nextTick. Since the promise implementation is considered platform code, it may itself contain a task-scheduling queue or “trampoline” in which the handlers are called.

上面一大段話的意思大致上就是要求 onFulfilledonRejected 回調(diào)函數(shù)確保異步執(zhí)行亡蓉。我們可以選擇用宏任務(wù)(setTimeout/setImmediate)或則微任務(wù)(process.nextTix/MutationObserver)來完成這項(xiàng)規(guī)范晕翠。

這里我們通過在Promise中的resolve和reject方法中套了一個setTimeout()來實(shí)現(xiàn)。

 function resolve(value){ 
    setTimeout(function(){ //2.2.4.
      if(self.status === PENDING){ //2.2.2.3-2.2.2.4
        self.status = FULFILLED; 
        self.value = value; 
        self.onFulfilledCallbacks.forEach(cb => cb(self.value)); //2.2.6.
      }
    })
  }

這樣setTimeout中的代碼就會在下一個新的執(zhí)行棧中執(zhí)行砍濒。即使executor中的代碼是同步代碼也一樣淋肾。

let p = new Promise((resolve,reject)=>{
  setTimeout(()=>{
    resolve('resolve');
  })
});
p.then((value)=>{
  console.log('fulfilled:',value);
},(reason)=>{
  console.log('rejected:',reason);
});
console.log('----------------');

//輸出
>>>----------------
>>>fulfilled: resolve
//----------------------------------------------------------------------------------
let p = new Promise((resolve,reject)=>{
    resolve('resolve');
});
p.then((value)=>{
  console.log('fulfilled:',value);
},(reason)=>{
  console.log('rejected:',reason);
});
console.log('----------------');

//輸出
>>>----------------
>>>fulfilled: resolve

情景:值的穿透

下面的例子中本應(yīng)是第一個then中的參數(shù)會穿透到第二then中作為參數(shù)。
下面兩句再集合resolvePromise方法即是穿透原因

Promise.prototype.then = function (onFulfilled, onRejected) { //2.2.1.
  //2.2.7.3-2.2.7.4 //2.2.5.
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; //結(jié)合resolvePromise方法即是穿透原因
  onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}; //繼續(xù)把異常往后拋
  ...

//-------------------------------------------------
let p = new Promise((resolve,reject)=>{
  resolve('resolve');
});
p.then().then((value)=>{
  console.log(value); //會輸出resolve
});

2.3. Promise狀態(tài)解析方法(promise resolution procedure)

let x= onRejected(reason);
resolvePromise(promise2,x,resolve,reject); //resolve/reject為promise2的resolve/reject

Promise狀態(tài)解析方法的作用是將then時返回的promise2的狀態(tài)改變并賦予其vlaue/reason爸邢。

  • 如果 x 是一個thenable樊卓,那么該方法將試圖將以 x 的狀態(tài)來改變 promise2 的狀態(tài)
  • 否則就將 promise2 改成 fulfilled 狀態(tài),并且value即為 x 的值

2.3.1. 如果 promise2x 是引用關(guān)系杠河,則拋出一個 TypeError 做為理由來 reject promise2碌尔。

2.3.2. 如果 x 是一個promise ,讓promise2采用它的狀態(tài)浇辜。

  • 如果 x 處于pending,promise2 必須保持pending直到 x 轉(zhuǎn)換為 fulfilled或則rejected七扰。
  • 如果 xfulfilled狀態(tài)奢赂,讓promise2也為fulfilled,并且讓promise2的value為x的value颈走。
  • 如果 xrejected狀態(tài)膳灶,讓promise2也為rejected,并且讓promise2的value為x的reason立由。

2.3.3. 如果 x 是一個對象或則函數(shù)

  • Let then be x.then
  • 如果檢索 x.then 時候拋出了一個異常e轧钓,那么以這個 erejecte promise2。
  • 如果 then 是一個函數(shù)锐膜,用x作為this毕箍,resolvePromise作為第一個參數(shù),rejectPromise作為第二個參數(shù)來 call它道盏。
    • 如果resolvePromise被調(diào)用而柑,循環(huán)調(diào)用 promise狀態(tài)解析方法(原本的x替換為調(diào)用resolvePromise傳入的參數(shù),假定為y)荷逞。
    • 如果rejectPromise被調(diào)用媒咳,則reject Promise2,reason為調(diào)用rejectPromise傳入的參數(shù)
    • 如果resolvePromiserejectPromise 同時被調(diào)用或則多次調(diào)用种远,那么第一個調(diào)用的擁有優(yōu)先權(quán)涩澡,其它的會被忽略。
    • 如果調(diào)用 then 的時候拋出了一個異常 e
      • 如果 resolvePromiserejectPromise 已經(jīng)被調(diào)用坠敷,則忽略它妙同。
      • 否則,則用這個ereject promise2膝迎。
  • 如果then不是一個函數(shù)粥帚,則用xfulfilledpromise2

2.3.4. 如果 x 不是一個函數(shù)也不是一個對象,則用xfulfilledpromise2

2.3.3. Promise實(shí)現(xiàn)

resolvePromise方法針對的是then綁定的回調(diào)函數(shù)中的return值進(jìn)行解析限次,一般情況是:

  • 當(dāng)return的是普通類型的值茎辐,那么會以這個值來fulfilled promise2
  • 如果是一個promise,那么會以這個x promise的結(jié)果來fulfilled/rejected promise2
function resolve(value) {
    if(value instanceof Promise){ //和resolvePromise有點(diǎn)聯(lián)系的是 當(dāng)then return的promise中又resolve了一個promise會先走這掂恕,會將resolve里的promise的值賦給調(diào)用resolve的promise(說法欠妥拖陆,意會即可)
      return value.then(resolve,reject); //這意味著如果promise1 resolve中是一個promise2,那么promise1狀態(tài)的改變時間會被推遲懊亡,直到promise2狀態(tài)改變調(diào)用promise2的回調(diào)時依啰,promise1狀態(tài)才會改變才會觸發(fā)promise1的回調(diào)
    }
...
//---------------------------------------------------------------------------------------------------------
function resolvePromise(promise2,x,resolve,reject){
  if(x === promise2){ //2.3.1.
    return reject(new TypeError('禁止循環(huán)引用!'));
  }
  let called =false;

  //2.3.2.
  if(x instanceof Promise){
    if(x.status === PENDING){ //2.3.2.1
      x.then((y)=>{
        resolvePromise(promise2,y,resolve,reject); //因?yàn)榇藭r的y,有可能也是一個promise //掛上一個鉤子只要x狀態(tài)轉(zhuǎn)化為成功態(tài)就遞歸調(diào)用resolvePromise
      },reject);
    }else{ //此分支存在的意義在于若executor調(diào)用resolve/reject不是異步的且不在resolve/reject中設(shè)置setTimeout店枣,意味著當(dāng)new的時候就會返回一個帶狀態(tài)的promise就會走這里速警。
      x.then(resolve,reject); //2.3.2.2-2.3.2.3 //只要x狀態(tài)改變叹誉,就以x的狀態(tài)和值來改變promise2的狀態(tài)和值 //這個值可能是一個promise,前提是在上面那種假設(shè)實(shí)現(xiàn)中 //如果不符合上面那種實(shí)現(xiàn)且不想像規(guī)范一樣允許值可以為一個promise或則對象 可除去此分支
    }
  }else if(x!=null&&((typeof x === 'function')||(typeof x === 'object'))){ //2.3.3.
    try{
      let then = x.then; //2.3.3.1

      if(typeof then === 'function'){
        //2.3.3.3.
        then.call(x,(y)=>{
          if(called) return; //2.3.3.3.3.
          called = true;
          resolvePromise(promise2,y,resolve,reject); //在resolve中又包含promise的情況下闷旧,由于resolve中的 value.then存在长豁,當(dāng)前回調(diào)調(diào)用時,resolve中的promise狀態(tài)一定已經(jīng)改變忙灼,在狀態(tài)已經(jīng)改變的時候利用then綁定回調(diào)匠襟,會走then中的status==fulfilled或則rejected分支
        },(reason)=>{
          if(called) return;
          called = true;
          reject(reason);
        });
      }else{
        resolve(x); //2.3.3.4. //1.3
      }
    }catch (e){
      if(called) return; //2.3.3.3.4.1.
      called = true;
      reject(e); //2.3.3.2. //2.3.3.3.4.2.
    }

  }else{ //2.3.4.
    resolve(x);
  }
}

情景:當(dāng)return的是promise且該promise的resolve/reject ()中 也是一個promise

let p = new Promise((resolve,reject)=>{
  resolve('resolve1');
});
p.then((value)=>{
  return new Promise((resolve,reject)=>{
    resolve(new Promise((resolve,reject)=>{
      setTimeout(()=>{
        resolve('別慫')
      });
    }));
  });
}).then((value)=>{
  console.log(value); //別慫
});
console.log('----------------');

可見最終的value值為最里層的value值,這樣的實(shí)現(xiàn)關(guān)鍵在于遞歸調(diào)用resolvePromise该园。

...
function resolve(value) {
    if(value instanceof Promise){ 
      return value.then(resolve,reject);
...

if(x instanceof Promise){
    if(x.status === PENDING){ //2.3.2.1
      x.then((y)=>{
        resolvePromise(promise2,y,resolve,reject); 
      },reject);
    }else{
      x.then(resolve,reject); 
    }
  }

以上這段代碼酸舍,當(dāng)promise1執(zhí)行回調(diào)的時候,會將x傳入resolvePromise執(zhí)行里初,此時由于resolve()方法中的setTimeout啃勉,該x是pending狀態(tài)進(jìn)pending分支,該分支會為X掛上一個鉤子双妨,當(dāng)它狀態(tài)轉(zhuǎn)換后會再次調(diào)用resolvePromise淮阐。

  • 如果x的resolve中傳入的也是一個promise (y),由于resolve中添加的value.then刁品,它會推遲x的狀態(tài)轉(zhuǎn)換枝嘶,這意味著X狀態(tài)轉(zhuǎn)換時,y的狀態(tài)一定已經(jīng)轉(zhuǎn)換哑诊,于是會走下面那個分支,調(diào)用y.then及刻,而因?yàn)閥的狀態(tài)已經(jīng)轉(zhuǎn)換镀裤,在then方法中此時就不再能通過狀態(tài)改變時觸發(fā)回調(diào)函數(shù),故要支持此功能需要在then中添加self.status===FULFILLED/REJECTED分支缴饭。
}else if(self.status === FULFILLED){
    return promise2 = new Promise(function(resolve,reject){
      setTimeout(function(){
        try{
          let x =onFulfilled(self.value);
          resolvePromise(promise2,x,resolve,reject);
        }catch(e){
          reject(e);
        }
      })

    });
  }else{
    return promise2 = new Promise(function(resolve,reject){
      setTimeout(function(){
        try{
          let x =onRejected(self.value);
          resolvePromise(promise2,x,resolve,reject);
        }catch(e){
          reject(e);
        }
      })
    });
  }

這里用了setTimeout是為了確笔钊埃回調(diào)函數(shù)會異步執(zhí)行。(針對2.2.4.)

  • 如果x的resolve傳入的只是一個普通的值颗搂。担猛。。呵呵噠丢氢,那就直接resolve(x)咯

[warning] 值得注意的是: 如果沒有在 resolve() 方法中對value進(jìn)行判斷傅联,那么此時嵌套promise中再嵌套一層promise輸出結(jié)果會是一個promise。因?yàn)榈诙€promise不會等第三個promise狀態(tài)轉(zhuǎn)換后才轉(zhuǎn)換狀態(tài)疚察,這意味著第二個promise的值就為第三個promise對象蒸走。

情景:當(dāng)new的promise中的resolve也是一個promise,而這個promise的resolve中又是一個promise...

此時情況同上個情景貌嫡,得益于then()中對value的判斷比驻,它會推遲父promise狀態(tài)的轉(zhuǎn)變该溯。
如果沒有這個判斷和推遲,那么也可能最終得到的value是個promise對象别惦。(這是規(guī)范允許的狈茉,但NodeJS和blubird對promise規(guī)范的實(shí)現(xiàn)都對父promise的狀態(tài)轉(zhuǎn)換進(jìn)行了推遲)

情景:在一個已經(jīng)轉(zhuǎn)換了狀態(tài)的promise中再次調(diào)用這個promise的then方法

此時也會走then中的self.status === FULFILLED/REJECTED 的分支,再次證明需要在then中添加這兩個分支并用上settimeout

p1.then((value)=>{ //執(zhí)行此回調(diào)時p1狀態(tài)已經(jīng)改變
    p1.then(...);
});

x instanceof Promise 和 typeof x=function... 遞歸的區(qū)別

instance分支下的遞歸 因?yàn)榇嬖趯romise狀態(tài)的判斷掸掸,當(dāng)resolve()沒有對value進(jìn)行判斷時氯庆,instance分支下的結(jié)果value最終可能為promise對象,而x.then分支下因?yàn)闆]有對promise狀態(tài)進(jìn)行判斷猾漫,故不會出現(xiàn)value為promise對象的情況点晴。

其余Promise方法的實(shí)現(xiàn)

Promise.prototype.catch

此方法實(shí)現(xiàn)灰常簡單,只需在最后一個then綁定完回調(diào)后再綁定一個錯誤的回調(diào)即可

promise.prototype.catch = function(onRejected){
    this.then(null,onRejected);
}

Promise.all

此方法傳入一組promise實(shí)例再返回一個最終的promise實(shí)例悯周,當(dāng)所有promise都轉(zhuǎn)為fulfilled時返回的最終的promise實(shí)例將會轉(zhuǎn)換為fulfilled粒督,此時這個promise的值為傳入的promise的值的集合。而如果傳入的那組promise中有一個rejected禽翼,返回的promise就會rejected屠橄。

Promise.all = function(promises){
    return new Promise((resolve,reject)=>{
        let result = [],
            count = 0;

        function done(i,data){
            result[i] = data;
            if(++count===promises.length){
                resolve(result);
            }
        }
        for(let i=0;i<promises.length;++i){
            promises[i].then((value)=>{
                done(i,value);
            },(reason)=>{
                reject(reason);
            });
        }
    });
}

Promise.race

也是傳入一組promise返回一個promise,哪個promise先轉(zhuǎn)換狀態(tài)闰挡,就返回這個promise的結(jié)果

Promise.race = function(promises){
    return new Promise((resolve,reject)=>{
        for(let i=0;i<promises.length;++){
            promises[i].then(resolve,reject);
        }
    });
}

Promise.promisify

將一個異步函數(shù)promise化锐墙,使其可以then,可以鏈?zhǔn)綍鴮?/p>

Promise.promisify = function(fn){
    return function(...args){
        return new Promise((resolve,reject)=>{
            fn.apply(null,[...args,function(err,data){
                err?reject(err):resolve(data);
            }]);
        });
    }
}

Promise.promisifyAll

將一個對象下的所有方法都promisify化

Promise.promisifyAll = function(obj){
    for(var attr in obj){
        if(obj.hasOwnProperty(key)&&typeof obj[attr]==='function'){
            obj[attr+'Async'] = Promise.promisify(obj[attr]);
        }
    }
}

測試

要對實(shí)現(xiàn)的Promise進(jìn)行測試长酗,除了實(shí)現(xiàn)t規(guī)范要求then方法和catch方法外還需要先在你的promise下添加一個方法

Promise.deferred = Promise.defer = function(){
  let defer = {};
  defer.promise = new Promise(function(resolve,reject){
    defer.resolve = resolve;
    defer.reject = reject;
  });
  return defer;
}

然后按下述進(jìn)行測試

npm i -g promises-aplus-tests
promises-aplus-tests yourFileName.js

實(shí)現(xiàn)代碼【終板】

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

function Promise(executor) {
  let self = this; //緩存下
  self.value = undefined; //用來存放value和reason,因?yàn)閜romise只會處于一種狀態(tài)故可只用一個變量來表示溪北。
  self.status = PENDING; //將初始狀態(tài)設(shè)置為pending
  self.onFulfilledCallbacks = []; //用來存放所有成功的回調(diào)函數(shù)
  self.onRejectedCallbacks = []; //用來存放所有失敗的回調(diào)函數(shù)

  try {
    executor(resolve, reject); //調(diào)用執(zhí)行函數(shù),將resolve和reject方法作為參數(shù)傳入
  } catch (e) {
    reject(e); //若執(zhí)行函數(shù)中存在異常直接用拋出的值來拒絕promise
  }

  function resolve(value) {
    if (value instanceof Promise) { //和resolvePromise有點(diǎn)聯(lián)系的是 當(dāng)then return的promise中又resolve了一個promise會先走這夺脾,會將resolve里的promise的值賦給調(diào)用resolve的promise(說法欠妥之拨,意會即可)
      return value.then(resolve, reject); //這意味著如果promise1 resolve中是一個promise2,那么promise1狀態(tài)的改變時間會被推遲咧叭,直到promise2狀態(tài)改變調(diào)用promise2的回調(diào)時蚀乔,promise1狀態(tài)才會改變才會觸發(fā)promise1的回調(diào)
    }

    setTimeout(function () {
      if (self.status === PENDING) {
        self.status = FULFILLED;
        self.value = value;
        self.onFulfilledCallbacks.forEach(cb => cb(self.value)); //2.2.2. //2.2.6.
      }
    })
  }

  function reject(reason) {
    setTimeout(function () {
      if (self.status === PENDING) {
        self.status = REJECTED;
        self.value = reason;
        self.onRejectedCallbacks.forEach(cb => cb(self.value)); //2.2.3. //2.2.6.
      }
    })
  }
}

Promise.prototype.then = function (onFulfilled, onRejected) { //2.2.1.
  //2.2.7.3-2.2.7.4 //2.2.5.
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
  onRejected = typeof onRejected === 'function' ? onRejected : reason => {
    throw reason
  };

  let self = this,
    promise2; //2.2.7.0 //聲明要返回的promise2

  if (self.status === PENDING) {
    //2.2.7.
    return promise2 = new Promise(function (resolve, reject) {
      //存儲then方法綁定的回調(diào)函數(shù) //2.2.6.
      self.onFulfilledCallbacks.push((value) => {
        try {
          let x = onFulfilled(value);
          resolvePromise(promise2, x, resolve, reject); //2.2.7.1 //resolve/reject屬于promise2 //若此方法執(zhí)行說明promise1狀態(tài)已經(jīng)更改
        } catch (e) {
          reject(e); //2.2.7.2
        }
      });
      self.onRejectedCallbacks.push((reason) => {
        try {
          let x = onRejected(reason);
          resolvePromise(promise2, x, resolve, reject);
        } catch (e) {
          reject(e);
        }
      });
    });
  } else if (self.status === FULFILLED) {
    return promise2 = new Promise(function (resolve, reject) {
      setTimeout(function () {
        try {
          let x = onFulfilled(self.value);
          resolvePromise(promise2, x, resolve, reject);
        } catch (e) {
          reject(e);
        }
      })

    });
  } else {
    return promise2 = new Promise(function (resolve, reject) {
      setTimeout(function () {
        try {
          let x = onRejected(self.value);
          resolvePromise(promise2, x, resolve, reject);
        } catch (e) {
          reject(e);
        }
      })
    });
  }

};

function resolvePromise(promise2, x, resolve, reject) {
  if (x === promise2) { //2.3.1.
    return reject(new TypeError('禁止循環(huán)引用!'));
  }
  let called = false;

  //2.3.2.
  if (x instanceof Promise) {
    if (x.status === PENDING) { //2.3.2.1
      x.then((y) => {
        resolvePromise(promise2, y, resolve, reject); //因?yàn)榇藭r的y,有可能也是一個promise //掛上一個鉤子只要x狀態(tài)轉(zhuǎn)化為成功態(tài)就遞歸調(diào)用resolvePromise
      }, reject);
    } else { //此分支存在的意義在于若executor調(diào)用resolve/reject不是異步的且不在resolve/reject中設(shè)置setTimeout菲茬,意味著當(dāng)new的時候就會返回一個帶狀態(tài)的promise就會走這里吉挣。
      x.then(resolve, reject); //2.3.2.2-2.3.2.3 //只要x狀態(tài)改變,就以x的狀態(tài)和值來改變promise2的狀態(tài)和值 //這個值可能是一個promise婉弹,前提是在上面那種假設(shè)實(shí)現(xiàn)中 //如果不符合上面那種實(shí)現(xiàn)且不想像規(guī)范一樣允許值可以為一個promise或則對象 可除去此分支
    }
  } else if (x != null && ((typeof x === 'function') || (typeof x === 'object'))) { //2.3.3.
    try {
      let then = x.then; //2.3.3.1

      if (typeof then === 'function') {
        //2.3.3.3.
        then.call(x, (y) => {
          if (called) return; //2.3.3.3.3.
          called = true;
          resolvePromise(promise2, y, resolve, reject); //在resolve中又包含promise的情況下睬魂,由于resolve中的 value.then存在,當(dāng)前回調(diào)調(diào)用時镀赌,resolve中的promise狀態(tài)一定已經(jīng)改變汉买,在狀態(tài)已經(jīng)改變的時候利用then綁定回調(diào),會走then中的status==fulfilled或則rejected分支
        }, (reason) => {
          if (called) return;
          called = true;
          reject(reason);
        });
      } else {
        resolve(x); //2.3.3.4. //1.3
      }
    } catch (e) {
      if (called) return; //2.3.3.3.4.1.
      called = true;
      reject(e); //2.3.3.2. //2.3.3.3.4.2.
    }

  } else { //2.3.4.
    resolve(x);
  }
}

Promise.deferred = Promise.defer = function () {
  let defer = {};
  defer.promise = new Promise(function (resolve, reject) {
    defer.resolve = resolve;
    defer.reject = reject;
  });
  return defer;
};

Promise.prototype.catch = function (onRejected) {
  this.then(null, onRejected)
};

Promise.resolve = function (value) {
  return new Promise((resolve, reject) => {
    resolve(value);
  })
};

Promise.reject = function (reason) {
  return new Promise((resolve, reject) => {
    reject(reason);
  })
};


Promise.all = function(promises){
  return new Promise((resolve,reject)=>{
    let result = [];
    let count = 0;
    function done(i,data){
      result[i] = data;
      if(++count === promises.length){
        resolve(result);
      }
    }
    for(let i=0;i<promises.length;++i){
      promises[i].then((value)=>{
        done(i,value);
      },reject);
    }
  })
};

Promise.race = function(promises){
  return new Promise(function(resolve,reject){
    for(let i=0;i<promises.length;++i){
      promises[i].then(resolve,reject);
    }
  });
};

Promise.promisify = function(fn){
  return function(...args){
    return new Promise((resolve,reject)=>{
      fn.apply(null,[...args,function(err,data){
        err?reject(err):resolve(data);
      }]);
    });
  }
};

Promise.promisifyALL = function(obj){
  for(var key in obj){
    if(obj.hasOwnProperty(key)&&typeof obj[key]=='function'){
      obj[key+'Async'] = Promise.promisify(obj[key]);
    }
  }
};
module.exports = Promise;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末佩脊,一起剝皮案震驚了整個濱河市蛙粘,隨后出現(xiàn)的幾起案子垫卤,更是在濱河造成了極大的恐慌,老刑警劉巖出牧,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件穴肘,死亡現(xiàn)場離奇詭異,居然都是意外死亡舔痕,警方通過查閱死者的電腦和手機(jī)评抚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伯复,“玉大人慨代,你說我怎么就攤上這事⌒ト纾” “怎么了侍匙?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長叮雳。 經(jīng)常有香客問我想暗,道長,這世上最難降的妖魔是什么帘不? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任说莫,我火速辦了婚禮,結(jié)果婚禮上寞焙,老公的妹妹穿的比我還像新娘储狭。我一直安慰自己,他們只是感情好捣郊,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布辽狈。 她就那樣靜靜地躺著,像睡著了一般模她。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上懂牧,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天侈净,我揣著相機(jī)與錄音,去河邊找鬼僧凤。 笑死畜侦,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的躯保。 我是一名探鬼主播旋膳,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼途事!你這毒婦竟也來了验懊?” 一聲冷哼從身側(cè)響起擅羞,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎义图,沒想到半個月后减俏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡碱工,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年娃承,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怕篷。...
    茶點(diǎn)故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡历筝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出廊谓,到底是詐尸還是另有隱情梳猪,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布蹂析,位于F島的核電站舔示,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏电抚。R本人自食惡果不足惜惕稻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蝙叛。 院中可真熱鬧俺祠,春花似錦、人聲如沸借帘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肺然。三九已至蔫缸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間际起,已是汗流浹背拾碌。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留街望,地道東北人校翔。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像灾前,于是被迫代替她去往敵國和親防症。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評論 2 353