手寫(xiě)Promise

Promise的聲明

首先翘鸭,promise肯定是一個(gè)類(lèi)坤按,我們就用class來(lái)聲明肴焊。

  • 由于new Promise((resolve, reject)=>{})其屏,所以傳入一個(gè)參數(shù)(函數(shù))唧瘾,promisesA+規(guī)范里叫他executor措译,傳入就執(zhí)行。
  • executor里面有兩個(gè)參數(shù)饰序,一個(gè)叫resolve(成功)领虹,一個(gè)叫reject(失敗)求豫。
  • 由于resolvereject可執(zhí)行塌衰,所以都是函數(shù),我們用let聲明蝠嘉。
class Promise {
  // 構(gòu)造器
  constructor(executor) {
    // 成功
    let resolve = () => { };
    // 失敗
    let reject = () => { };
    // 立即執(zhí)行
    executor(resolve, reject);
  }
}

解決基本狀態(tài)

promisesA+規(guī)范對(duì)Promise有規(guī)定:

  • Promise存在三個(gè)狀態(tài)(state):pending最疆、fulfilledrejected
  • pending(等待態(tài))為初始態(tài)蚤告,并可以轉(zhuǎn)化為fulfilled(成功態(tài))和rejected(失敗態(tài))
  • 成功時(shí)努酸,不可轉(zhuǎn)為其他狀態(tài),且必須有一個(gè)不可改變的值(value
  • 失敗時(shí)杜恰,不可轉(zhuǎn)為其他狀態(tài)获诈,且必須有一個(gè)不可改變的原因(reason
  • new Promise((resolve, reject)=>{resolve(value)})resolve為成功箫章,接收參數(shù)value烙荷,狀態(tài)改變?yōu)?code>fulfilled,不可再次改變
  • new Promise((resolve, reject)=>{reject(reason)})檬寂,reject為失敗终抽,接收參數(shù)reason,狀態(tài)改變?yōu)?code>rejected桶至,不可再次改變
  • 若是executor函數(shù)報(bào)錯(cuò)昼伴,直接執(zhí)行reject()

于是乎,我們獲得以下代碼:

class Promise {
  constructor(executor) {
    // 初始化state為等待態(tài)
    this.state = 'pending';
    // 成功的值
    this.value = undefined;
    // 失敗的原因
    this.reason = undefined;
    let resolve = value => {
      // state改變,resolve調(diào)用就會(huì)失敗
      if (this.state === 'pending') {
        // resolve調(diào)用后镣屹,state轉(zhuǎn)化為成功態(tài)
        this.state = 'fulfilled';
        // 儲(chǔ)存成功的值
        this.value = value;
      }
    };
    let reject = reason => {
      // state改變,reject調(diào)用就會(huì)失敗
      if (this.state === 'pending') {
        // reject調(diào)用后圃郊,state轉(zhuǎn)化為失敗態(tài)
        this.state = 'rejected';
        // 儲(chǔ)存失敗的原因
        this.reason = reason;
      }
    };
    // 如果executor執(zhí)行報(bào)錯(cuò),直接執(zhí)行reject
    try{
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
}

then方法

promisesA+規(guī)范規(guī)定:Promise有一個(gè)叫做then的方法女蜈,里面有兩個(gè)參數(shù):onFulfilled, onRejected持舆,成功有成功的值色瘩,失敗有失敗的原因。

  • 當(dāng)狀態(tài)statefulfilled逸寓,則執(zhí)行onFulfilled居兆,傳入this.value。當(dāng)狀態(tài)staterejected竹伸,則執(zhí)行onRejected泥栖,傳入this.reason
  • onFulfilled, onRejected如果他們是函數(shù),則必須分別在fulfilled勋篓,rejected后被調(diào)用吧享,valuereason依次作為他們的第一個(gè)參數(shù)
class Promise {
  constructor(executor) {...}
  // then 方法 有兩個(gè)參數(shù)onFulfilled onRejected
  then(onFulfilled, onRejected) {
    // 狀態(tài)為fulfilled,執(zhí)行onFulfilled譬嚣,傳入成功的值
    if (this.state === 'fulfilled') {
      onFulfilled(this.value);
    };
    // 狀態(tài)為rejected钢颂,執(zhí)行onRejected,傳入失敗的原因
    if (this.state === 'rejected') {
      onRejected(this.reason);
    };
  }
}

解決異步實(shí)現(xiàn)

現(xiàn)在基本可以實(shí)現(xiàn)簡(jiǎn)單的同步代碼孤荣,但是當(dāng)resolvesetTomeout內(nèi)執(zhí)行甸陌,then時(shí)state還是pending等待狀態(tài) 我們就需要在then調(diào)用的時(shí)候,將成功和失敗存到各自的數(shù)組盐股,一旦reject或者resolve钱豁,就調(diào)用它們。
類(lèi)似于發(fā)布訂閱疯汁,先將then里面的兩個(gè)函數(shù)儲(chǔ)存起來(lái)牲尺,由于一個(gè)promise可以有多個(gè)then,所以存在同一個(gè)數(shù)組內(nèi)幌蚊。

// 多個(gè)then的情況
let p = new Promise();
p.then();
p.then();

成功或者失敗時(shí)谤碳,forEach調(diào)用它們。

class Promise{
  constructor(executor){
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    // 成功存放的數(shù)組
    this.onResolvedCallbacks = [];
    // 失敗存放法數(shù)組
    this.onRejectedCallbacks = [];
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        // 一旦resolve執(zhí)行溢豆,調(diào)用成功數(shù)組的函數(shù)
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    };
    let reject = reason => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        // 一旦reject執(zhí)行蜒简,調(diào)用失敗數(shù)組的函數(shù)
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    };
    try{
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled,onRejected) {
    if (this.state === 'fulfilled') {
      onFulfilled(this.value);
    };
    if (this.state === 'rejected') {
      onRejected(this.reason);
    };
    // 當(dāng)狀態(tài)state為pending時(shí)
    if (this.state === 'pending') {
      // onFulfilled傳入到成功數(shù)組
      this.onResolvedCallbacks.push(()=>{
        onFulfilled(this.value);
      })
      // onRejected傳入到失敗數(shù)組
      this.onRejectedCallbacks.push(()=>{
        onRejected(this.reason);
      })
    }
  }
}

解決鏈?zhǔn)秸{(diào)用

我門(mén)常常用到new Promise().then().then(),這就是鏈?zhǔn)秸{(diào)用,用來(lái)解決回調(diào)地獄漩仙。
為了達(dá)成鏈?zhǔn)酱瓴纾覀兡J(rèn)在第一個(gè)then里返回一個(gè)promisepromisesA+規(guī)范規(guī)定了一種方法队他,就是在then里面返回一個(gè)新的promise卷仑,稱(chēng)為promise2promise2 = new Promise((resolve, reject)=>{})

  • 將這個(gè)promise2返回的值傳遞到下一個(gè)then
  • 如果返回一個(gè)普通的值,則將普通的值傳遞給下一個(gè)then

當(dāng)我們?cè)诘谝粋€(gè)thenreturn了一個(gè)參數(shù)(參數(shù)未知麸折,需判斷)锡凝。這個(gè)return出來(lái)的新的promise就是onFulfilled()onRejected()的值。

promisesA+規(guī)范則規(guī)定onFulfilled()onRejected()的值垢啼,即第一個(gè)then返回的值窜锯,叫做x张肾,判斷x的函數(shù)叫做resolvePromise

  • 首先衬浑,要看x是不是promise
  • 如果是promise捌浩,則取它的結(jié)果,作為新的promise2成功的結(jié)果
  • 如果是普通值工秩,直接作為promise2成功的結(jié)果
  • 所以要比較xpromise2
  • resolvePromise的參數(shù)有promise2(默認(rèn)返回的promise)、x(我們自己return的對(duì)象)进统、resolve助币、reject
  • resolverejectpromise2
class Promise {
  constructor(executor) { ... }
  then(onFulfilled, onRejected) {
    // 聲明返回的promise2
    let promise2 = new Promise((resolve, reject)=>{
      if (this.state === 'fulfilled') {
        let x = onFulfilled(this.value);
        // resolvePromise函數(shù),處理自己return的promise和默認(rèn)的promise2的關(guān)系
        resolvePromise(promise2, x, resolve, reject);
      };
      if (this.state === 'rejected') {
        let x = onRejected(this.reason);
        resolvePromise(promise2, x, resolve, reject);
      };
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(()=>{
          let x = onFulfilled(this.value);
          resolvePromise(promise2, x, resolve, reject);
        })
        this.onRejectedCallbacks.push(()=>{
          let x = onRejected(this.reason);
          resolvePromise(promise2, x, resolve, reject);
        })
      }
    });
    // 返回promise螟碎,完成鏈?zhǔn)?    return promise2;
  }
}

完成resolvePromise函數(shù)

promisesA+規(guī)范規(guī)定了一段代碼眉菱,讓不同的promise代碼互相套用,叫做resolvePromise掉分。
如果x === promise2俭缓,則是會(huì)造成循環(huán)引用,自己等待自己完成酥郭,則報(bào)“循環(huán)引用”錯(cuò)誤华坦。

let p = new Promise(resolve => {
  resolve(0);
});
var p2 = p.then(data => {
  // 循環(huán)引用,自己等待自己完成不从,一輩子完不成
  return p2;
})
  1. 判斷x
  • x不能是null
  • x是普通值 直接resolve(x)
  • x是對(duì)象或者函數(shù)(包括promise)惜姐,let then = x.then
  1. 當(dāng)x是對(duì)象或者函數(shù)(默認(rèn)promise
  • 聲明了then
  • 如果取then報(bào)錯(cuò),則走reject()
  • 如果then是個(gè)函數(shù)椿息,則用call執(zhí)行then歹袁,第一個(gè)參數(shù)是this,后面是成功的回調(diào)和失敗的回調(diào)
  • 如果成功的回調(diào)還是pormise寝优,就遞歸繼續(xù)解析
  1. 成功和失敗只能調(diào)用一個(gè)条舔,所以設(shè)定一個(gè)called來(lái)防止多次調(diào)用
function resolvePromise(promise2, x, resolve, reject){
  // 循環(huán)引用報(bào)錯(cuò)
  if(x === promise2){
    // reject報(bào)錯(cuò)
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  // 防止多次調(diào)用
  let called;
  // x不是null 且x是對(duì)象或者函數(shù)
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      // A+規(guī)定,聲明then = x的then方法
      let then = x.then;
      // 如果then是函數(shù)乏矾,就默認(rèn)是promise了
      if (typeof then === 'function') { 
        // 就讓then執(zhí)行 第一個(gè)參數(shù)是this   后面是成功的回調(diào) 和 失敗的回調(diào)
        then.call(x, y => {
          // 成功和失敗只能調(diào)用一個(gè)
          if (called) return;
          called = true;
          // resolve的結(jié)果依舊是promise 那就繼續(xù)解析
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          // 成功和失敗只能調(diào)用一個(gè)
          if (called) return;
          called = true;
          reject(err);// 失敗了就失敗了
        })
      } else {
        resolve(x); // 直接成功即可
      }
    } catch (e) {
      // 也屬于失敗
      if (called) return;
      called = true;
      // 取then出錯(cuò)了那就不要在繼續(xù)執(zhí)行了
      reject(e); 
    }
  } else {
    resolve(x);
  }
}

解決其他問(wèn)題

  1. promisesA+規(guī)范規(guī)定onFulfilled, onRejected都是可選參數(shù)孟抗,如果他們不是函數(shù),必須被忽略
  • onFulfilled返回一個(gè)普通的值妻熊,成功時(shí)直接等于value => value
  • onRejected返回一個(gè)普通的值夸浅,失敗時(shí)如果直接等于value => value,則會(huì)跑到下一個(gè)then中的onFulfilled中扔役,所以直接扔出一個(gè)錯(cuò)誤reason => throw err
  1. promisesA+規(guī)范規(guī)定onFulfilledonRejected不能同步被調(diào)用帆喇,必須異步調(diào)用。我們就用setTimeout解決異步問(wèn)題
  • 如果onFulfilledonRejected報(bào)錯(cuò)亿胸,則直接返回reject()
class Promise{
  constructor(executor){
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    };
    let reject = reason => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    };
    try{
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled,onRejected) {
    // onFulfilled如果不是函數(shù)坯钦,就忽略onFulfilled预皇,直接返回value
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    // onRejected如果不是函數(shù),就忽略onRejected婉刀,直接扔出錯(cuò)誤
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
    let promise2 = new Promise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        // 異步
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      if (this.state === 'rejected') {
        // 異步
        setTimeout(() => {
          // 如果報(bào)錯(cuò)
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      if (this.state === '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)
        });
      };
    });
    // 返回promise吟温,完成鏈?zhǔn)?    return promise2;
  }
}

catchresolve、reject突颊、race和all方法

class Promise {
  constructor(executor) {
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    };
    let reject = reason => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    };
    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled,onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
    let promise2 = new Promise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      if (this.state === 'rejected') {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      if (this.state === '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;
  }
  catch(fn){
    return this.then(null,fn);
  }
}
function resolvePromise(promise2, x, resolve, reject){
  if(x === promise2){
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  let called;
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      let then = x.then;
      if (typeof then === 'function') { 
        then.call(x, y => {
          if(called)return;
          called = true;
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          if(called)return;
          called = true;
          reject(err);
        })
      } else {
        resolve(x);
      }
    } catch (e) {
      if(called)return;
      called = true;
      reject(e); 
    }
  } else {
    resolve(x);
  }
}
//resolve方法
Promise.resolve = function(val){
  return new Promise((resolve,reject)=>{
    resolve(val)
  });
}
//reject方法
Promise.reject = function(val){
  return new Promise((resolve,reject)=>{
    reject(val)
  });
}
//race方法 
Promise.race = function(promises){
  return new Promise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(resolve,reject)
    };
  })
}
//all方法(獲取所有的promise鲁豪,都執(zhí)行then,把結(jié)果放到數(shù)組律秃,一起返回)
Promise.all = function(promises){
  let arr = [];
  let i = 0;
  function processData(index,data){
    arr[index] = data;
    i++;
    if(i == promises.length){
      resolve(arr);
    };
  };
  return new Promise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(data=>{
        processData(i,data);
      },reject);
    };
  });
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末爬橡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子棒动,更是在濱河造成了極大的恐慌糙申,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件船惨,死亡現(xiàn)場(chǎng)離奇詭異柜裸,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)粱锐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)疙挺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人卜范,你說(shuō)我怎么就攤上這事衔统。” “怎么了海雪?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵锦爵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我奥裸,道長(zhǎng)险掀,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任湾宙,我火速辦了婚禮樟氢,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘侠鳄。我一直安慰自己埠啃,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布伟恶。 她就那樣靜靜地躺著碴开,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上潦牛,一...
    開(kāi)封第一講書(shū)人閱讀 52,441評(píng)論 1 310
  • 那天眶掌,我揣著相機(jī)與錄音,去河邊找鬼巴碗。 笑死朴爬,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的橡淆。 我是一名探鬼主播召噩,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼逸爵!你這毒婦竟也來(lái)了蚣常?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤痊银,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后施绎,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體溯革,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嫉鲸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年扭仁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赚窃。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡俱尼,死狀恐怖抖单,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情遇八,我是刑警寧澤矛绘,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站刃永,受9級(jí)特大地震影響货矮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜斯够,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一囚玫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧读规,春花似錦抓督、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至枪汪,卻和暖如春涌穆,著一層夾襖步出監(jiān)牢的瞬間怔昨,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工宿稀, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留趁舀,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓祝沸,卻偏偏與公主長(zhǎng)得像矮烹,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子罩锐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

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