深入瞭解promise機制,手動擼一個promise

promise 機制

關於前端架構只是體系中的知識剩下的部分一直沒有不全,不是不想寫,實在是不敢輕易下筆,如果深度不夠,我想關於es6的只是點我們都可以參考 阮一峯的es6-promise教程, 如果需要一定的深度,不僅僅需要查找一些資料,更重要的是還要吸收而後進一步整理。所以跟新完善的進展有點慢,但是,要麼不做,要麼就做好。我相信當您閱讀完本文,關於 promise 相信您會有一個全新的認識。

溫馨提示:
本文適合對es6是相對熟悉的看官閱讀,如果您對es6還不是那麼瞭解,建議先看完 阮一峯老師es6-promise教程 後再閱讀本文

爲了讓您讀起來不枯燥,咱們一邊看題,一邊思考,一邊完善

  • 先看最基礎的使用
new Promise((resolve,reject)=>{
  // resolve() 
  // reject();
})

分析: 通過上面代碼不難看書,Promise 就是一個構造函數,並且接受兩個方法作爲參數
我們的雛形代碼如下:

Promise1 function(excutor) {
  function resolve(value){}
  function reject(value) {}
  exctuor(resolve, reject)
}
  • 官方文檔告訴我們說(1)對象的狀態不受外界影響。(2)一旦狀態改變,就不會再變,任何時候都可以得到這個結果
var p1 = new Promise(function(resolve, reject) {
    resolve(1)
    throw Error('sync error')
})
.then(res => {
   console.log(11111,res) // 11111 1
}, (error) => {console.log(error)})
.catch(err => {
   console.log(err)
})
// 控制檯輸出 11111 1

分析:結合官方的說法和實際代碼的運行結果,我們猜測,狀態不僅僅是內部私有的,而且改變之後不可逆,根據 es6 中的說法三種狀態分別爲 pending(進行中)、fulfilled(已成功)和rejected(已失敗)

進一步改善我們的代碼

Promise1 function(excutor) {
  let self = this; // 保持良好的習慣,避免一些不要的坑
  self.status = 'pending'; // 初始狀態爲pending
  self.value = null; // 返回值默認爲null
  self.reason = null; // 錯誤信息默認爲null
  function resolve(value){
  	// 不可逆操作
  	if (self.status === 'pending') {
  		self.status = 'fulfilled';
  		self.value = value;
  	}
  }
  function reject(reason) {
  	// 不可逆操作
  	if (self.status === 'pending') {
  		self.status = 'rejected'
  		self.reason = reason;
  	}
  }
  // 爲了避免 resolve 和 reject 方法本身出錯,我們容錯一下
  try {
    exctuor(resolve, reject)
  }catch(err) {
  	reject(err) // 通過reject來記錄錯誤信息
  }
}

Promise1.prototype.then = function (onFulfilled, onRejected) {
  let self = this;
  // then 方法後面的成功的回調
  if (self.status === 'fulfilled') {
  	try{
  		onFulfilled(self.value) // 返回值
  	}catch(error) {
  		self.reject(error)
  	}
  }
  // then 方法後面的失敗的回調
  if (self.status === 'rejected') {
  	try{
  		onRejected(self.value) // 返回值
  	}catch(error) {
  		self.reject(error)
  	}
  }
  if (self.status === 'pending') {
  	
  }
}
  • Promise 是異步編程的一種解決方案
    看代碼
new Promise(function(resolve, reject) {
  //使用定時器來模擬異步
  setTimeout(() => {
    resolve(100);
  }, 1000);
}).then(function(value) {
  console.log(11111111, value);
});
// 一秒之後,控制檯仍然答應出了 11111111 100

但是根據我們上面的實現1000之後,整個函數體內的上下就消失了,那麼這個在設計上顯然不合理,怎麼辦呢?那我們就先保存一下,等到整個運行結束再來執行,代碼進一步完善如下:

Promise1 function(excutor) {
  let self = this; // 保持良好的習慣,避免一些不要的坑
  self.status = 'pending'; // 初始狀態爲pending
  self.value = null; // 返回值默認爲null
  self.reason = null; // 錯誤信息默認爲null
  self.onFulfilledCallbacks = []; // 成功回調的隊列
  self.onRejectedCallbacks = []; // 失敗回調的隊列
  function resolve(value){
  	// 不可逆操作
  	if (self.status === 'pending') {
  		self.status = 'fulfilled';
  		self.value = value;
  	}
  }
  function reject(reason) {
  	// 不可逆操作
  	if (self.status === 'pending') {
  		self.status = 'rejected'
  		self.reason = reason;
  	}
  }
  // 爲了避免 resolve 和 reject 方法本身出錯,我們容錯一下
  try {
    exctuor(resolve, reject)
  }catch(err) {
  	reject(err) // 通過reject來記錄錯誤信息
  }
}

Promise1.prototype.then = function (onFulfilled, onRejected) {
  let self = this;
  // then 方法後面的成功的回調
  if (self.status === 'fulfilled') {
  	try{
  		onFulfilled(self.value) // 返回值
  	}catch(error) {
  		self.reject(error)
  	}
  }
  // then 方法後面的失敗的回調
  if (self.status === 'rejected') {
  	try{
  		onRejected(self.reason) // 返回值
  	}catch(error) {
  		self.reject(error)
  	}
  }
  // 當狀態處於pending狀態時,像隊列中添加各自的回調,然後在同一執行
  if (self.status === 'pending') {
  	self.onFulfilledCallbacks.push(() => {
  	 try {
  	 	onFulfilled(self.value)
  	 }catch(error){
  	 	self.reject(err);
  	 }
  	})
  	self.onFulfilledCallbacks.push(() => {
  	 try {
  	 	onRejected(self.reason)
  	 }catch(error){
  	 	self.reject(err);
  	 }
  	})
  }
}
  • 到目前爲止,看起來基本的promise結構已經出來了,但是promise是可以鏈式調用的,所謂的鏈式調用的本質上就是每一個方法都返回一個新的promise 實例,這個就相對簡單了,在我們所有的參數方法外面包一層 new Promise() 就好了
    代碼如下:
Promise1.prototype.then = function(onFulfilled, onRejected) {
  let self = this;
  if (self.status === "fulfilled") {
    // 返回新的Promise
    return new Promise1((resolve, reject) => {
      try {
      	// 如果不指定錯誤的回調函數,會再次拋出錯誤,
        let x = onFulfilled(self.value);
      } catch (err) {
        reject(err);
      }
    });
  }

  if (self.status === "rejected") {
    return new Promise1((resolve, reject) => {
      try {
        let x = onRejected(self.reason); 
      } catch (err) {
        reject(err);
      }
    });
  }

  if (self.status === "pending") {
    return new Promise1((resolve, reject) => {
      self.onFulfilledCallbacks.push(() => {
        let x = onFulfilled(self.value);
      });
      self.onRejectedCallbacks.push(() => {
        let x = onRejected(self.reason);
      });
    });
  }
  • 聽所then的回調函數還可以繼續返回promise
new Promise(function(resolve, reject) {
  // TODO
  resolve(11111);
}).then(res => {
  return new Promise(function(resolve, reject) {
    resolve(res);
  }).then(res => {
    console.log(2222, res);
  });
});
// 控制檯輸出 2222 11111

這樣的話,我們上面的代碼還不夠,的進一步判斷返回的是否是一個promise實例
代碼如下:

Promise1.prototype.then = function(onFulfilled, onRejected) {
  let self = this;
  if (self.status === "fulfilled") {
    // 返回新的Promise
    return new Promise1((resolve, reject) => {
      try {
      	// 如果不指定錯誤的回調函數,會再次拋出錯誤,
        let x = onFulfilled(self.value);
        // 針對返回做判斷,如果是一個Promise對象,就繼續 .then 保持整個函數的鏈式結構,否則就調用 resolve 方法
        x instanceof Promise1 && x.then(resolve, reject) : resolve(x)
      } catch (err) {
        reject(err);
      }
    });
  }

  if (self.status === "rejected") {
    return new Promise1((resolve, reject) => {
      try {
        let x = onRejected(self.reason); 
         x instanceof Promise1 ? x.then(resolve, reject) : resolve(x);
      } catch (err) {
        reject(err);
      }
    });
  }

  if (self.status === "pending") {
    return new Promise1((resolve, reject) => {
      self.onFulfilledCallbacks.push(() => {
        let x = onFulfilled(self.value);
        x instanceof Promise1 ? x.then(resolve, reject) : resolve(x);
      });
      self.onRejectedCallbacks.push(() => {
        let x = onRejected(self.reason);
        x instanceof Promise1 ? x.then(resolve, reject) : resolve(x);
      });
    });
  }

到目前爲止,整個 Promise 的代碼 如下:

function Promise1(excutor) {
  let self = this;
  self.status = "pending";
  self.value = null;
  self.reason = null;
  self.onFulfilledCallbacks = []; // 成功回調的隊列
  self.onRejectedCallbacks = []; // 失敗回調的隊列

  // 什麼時候調用,什麼時候改變狀態,且狀態不可逆
  function resolve(value) {
    if (self.status === "pending") {
      self.value = value;
      self.status = "fulfilled";
      // 以下方法會在先調用then回調之後,在resolve的時候執行
      self.onFulfilledCallbacks.length && self.onFulfilledCallbacks.forEach(item => item());
    }
  }

  function reject(reason) {
    if (self.status === "pending") {
      self.reason = reason;
      self.status = "rejected";
      self.onFulfilledCallbacks.length && self.onRejectedCallbacks.forEach(item => item());
    }
  }

  // 這裏可以捕獲一些不必要的錯誤,eg: excute 不是一個 funciton,或者 excute 內部出錯
  try {
    excutor(resolve, reject); // 構造函數傳進來的函數
  } catch (err) {
    reject(err);
  }
}

Promise1.prototype.then = function(onFulfilled, onRejected) {
  let self = this;

  if (self.status === "fulfilled") {
    // 返回新的Promise
    return new Promise1((resolve, reject) => {
      try {
        let x = onFulfilled(self.value);
        // then 的回調也有可以返回一個Promise
        x instanceof Promise1 ? x.then(resolve, reject) : resolve(x);
      } catch (err) {
        reject(err);
      }
    });
  }

  if (self.status === "rejected") {
    return new Promise1((resolve, reject) => {
      try {
        let x = onRejected(self.reason); // 如果不指定錯誤的回調函數,會再次拋出錯誤,
        x instanceof Promise1 ? x.then(resolve, reject) : resolve(x);
      } catch (err) {
        reject(err);
      }
    });
  }

  if (self.status === "pending") {
    return new Promise1((resolve, reject) => {
      self.onFulfilledCallbacks.push(() => {
        let x = onFulfilled(self.value);
        x instanceof Promise1 ? x.then(resolve, reject) : resolve(x);
      });
      self.onRejectedCallbacks.push(() => {
        let x = onRejected(self.reason);
        x instanceof Promise1 ? x.then(resolve, reject) : resolve(x);
      });
    });
  }
};
  • 此時此刻官方又告訴我們說 Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的別名,用於指定發生錯誤時的回調函數

這就帥歪歪了啊,啥都不用謝直接複製就好了

promise 添加 catch 方法:

Promise1.prototype.catch = function(errorCallBack) {
  return this.then(null, errorCallBack);
};

另外的兩個方法也直接附上

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

Promise1.resolve = function(value) {
  return new Promise1((resolve, reject) => {
    resolve(value);
  });
};
Promise.prototype.finally = function(callback) {
  let self = this.constructor;
  return self.then(
    value => self.resolve(callback()).then(() => value),
    reason =>
      self.resolve(callback()).then(() => {
        throw reason;
      })
  );
};

到此爲止整個 手動 擼 Promise 的過程全部結束。當然相信你對 promise 有了更加深刻的理解

接下來上一組 promise 相關的面試題來熱熱身,促進一下消化
思考一下一面三組代碼的輸出結果
promise 捕獲錯誤:
🌰1:

new Promise(function(resolve, reject) {
    throw Error('sync error')
})
.then(res => {
   console.log(res)
})
.catch(err => {
    console.log(err)
})

🌰2:

new Promise(function(resolve, reject) {
    setTimeout(() => {
        throw Error('async error')   
    })
})
.then(res => {
    console.log(res)
})
.catch(err => {
    console.log(err)
})

🌰3:

 new Promise(function(resolve, reject) {
    resolve()
})
.then(res => {
   throw Error('sync error') 
})

正確答案爲:

  • 第一組錯誤被catch 捕獲,控制檯輸出錯誤信息 Error: sync error
  • 第二組的定時器拋出錯誤的時候,catch 已失效,無法正確捕獲錯誤,控制檯直接報錯
  • 第三組拋出錯誤信息在promise內部,但是由於沒有被catch捕獲,直接拋出到外部,控制檯直接報錯

將上面的代碼稍作修改,再來一次
promise 狀態一旦發生改變不可逆轉:
🌰1:

new Promise(function(resolve, reject) {
    resolve(1)
    throw Error('sync error')
})
.then(res => {
        console.log(res)
})
.catch(err => {
        console.log(err)
})

🌰2:

new Promise(function(resolve, reject) {
    reject(2)
    resolve(1)
})
.then(res => {
        console.log(res)
})
.catch(err => {
        console.log(err)
})

🌰3:

new Promise(function(resolve, reject) {
    resolve(1)
})
.then(res => {
     throw Error('sync error')
     console.log(res)
})
.catch(err => {
        console.log(err)
})

正確答案爲:

  • 第一組,控制檯輸出1, 一旦狀態發生改變不可逆轉,後面的代碼得不到執行
  • 第二組,控制檯輸出2, 原因同上
  • 第三組,then 內部拋出的錯誤被外部捕獲,控制檯打印出錯誤信息

咱們繼續,還不夠爽
promise 中的回調是異步的:

new Promise(function(resolve, reject) {
    resolve()
    setTimeout(() => {
        console.log(1)
    })
    console.log(2)
})
.then(res => {
        console.log(3)
})
console.log(4)

答案: 1432
new promise 是屬於構造函數,會立即執行,控制檯先輸出3
setTimeout 延時,在空閒是纔會執行 最後輸出 1
then 方法也是會在整個promise 的回調結束之後執行,而且是一個異步的,所以會處於等待狀態,代碼繼續向下執行, 控制檯打印 4 ,而後再返回來執行then中的回調,輸出 3

promise 返回一個新的promise(鏈式調用):

new Promise(function(resolve, reject) {
    reject(1)
})
.then(res => {
  console.log(res)
  return 2
},err => {
  console.log(err) // 輸出 1
  return 3
 }
)
.catch(err => {
  // 有err回調,這一步跳過
  console.log(err)
  return 4
})
.finally(res => {
   // 該函數內部的代碼執行和狀態以及返回值無關
   console.log(res) // 直接返回 undefined 參見 Promise.prototype.finally
   return 5
})
.then(
    res => console.log(res), // err 回調中返回了新的值爲3,所以執行成功的回調
    err => console.log(err)
)

輸出結果:

  • 1
  • undefined
  • 3
    原因已經進行了說明,就不在解析

希望對各位看官有所幫助

其它前端性能優化:

前端技術架構體系(沒有鏈接的後續跟進):

其它相關

歡迎各位看官的批評和指正,共同學習和成長
希望該文章對您有幫助,你的 支持和鼓勵會是我持續的動力

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章