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
原因已經進行了說明,就不在解析
希望對各位看官有所幫助
其它前端性能優化:
- 圖片優化——質量與性能的博弈
- 瀏覽器緩存機制介紹與緩存策略剖析
- webpack 性能調優與 Gzip 原理
- 本地存儲——從 Cookie 到 Web Storage、IndexDB
- CDN 的緩存與回源機制解析
- 服務端渲染的探索與實踐
- 解鎖瀏覽器背後的運行機制
- DOM 優化原理與基本實踐
- Event Loop 與異步更新策略
- 迴流(Reflow)與重繪(Repaint)
- Lazy-Load
- 事件的節流(throttle)與防抖(debounce
- 前端學習資料下載
- 技術體系分類
前端技術架構體系(沒有鏈接的後續跟進):
- 調用堆棧
- 作用域閉包
- this全面解析
- 深淺拷貝的原理
- 原型prototype
- 事件機制、
- Event Loop
- Promise機制
- async / await原理、
- 防抖/節流原理
- 模塊化詳解、
- es6重難點、
- 瀏覽器薰染原理
- webpack配置(原理)
- 前端監控、
- 跨域和安全、
- 性能優化
- VirtualDom原理、
- Diff算法、
- 數據的雙向綁定
- [TCP協議(三次握手、四次揮手)](https://blog.csdn.net/woleigequshawanyier/article/details/85223642
- DNS域名解析
其它相關
歡迎各位看官的批評和指正,共同學習和成長
希望該文章對您有幫助,你的 支持和鼓勵會是我持續的動力