學習資料:拉勾課程《大前端高薪訓練營》
閱讀建議:文章較長,搭配文章的側邊欄目錄進行食用,體驗會更佳哦!
Promise物件是一個可以管理同步 / 異步操作運行結果的狀態和資料的容器,它的核心邏輯就是外部操作合理地把它的運行結果狀態和資料交由容器存盤并管理,而后外部再從容器中取出該外部操作的狀態和資料進行下一步操作,
本文接下來從以下手寫Promise的五個關鍵認識開始探討,最終完成手寫Promise:
- Promise容器的基本模樣(容器的屬性和行為)
- Promise容器的構造方式(容器托管excutor函式的運行結果狀態和資料)
- 往Promise容器中寫入資料(resolve、reject函式負責)
- 從Promise容器中讀取資料(then方法負責)
- 實作then函式的鏈式呼叫
一:Promise容器的基本模樣(容器的屬性和行為)
1.容器管理的資料(屬性)
- 狀態state:pending、resolved / fulfilled、rejected
- 結果value:容器resolved / fulfilled狀態下的資料
- 拒因reason:容器rejected狀態下的資料
- onResolvedTodoList:pending狀態時添加,容器resolved / fulfilled狀態后的回呼行為陣列
- onRejectedTodoList:pending狀態時添加,容器rejected狀態后的回呼行為陣列
2.容器資料的讀寫(行為)
- 寫操作:容器內部定義一個可以設定內部狀態和資料的resolve和reject方法,以供外部為容器中寫入資料(value / reason),實際上,Promise容器對寫操作有約束,它只允許初始化寫操作,不允許更改寫操作,
- 讀操作:容器內部定義一個可以讀取內部狀態和資料的then方法,以供外部讀取容器中的狀態和資料后觸發相應的回呼行為,
3.代碼實作
class Container {
state = undefined;
value = undefined;
reason = undefined;
constructor(excutor) { // 構造容器 }
resolve = value => { // 寫容器資料 }
reject = reason => { // 寫容器資料 }
onResolvedTodoList = [];
onRejectedTodoList = [];
then(onResolved, onRejected) { // 讀容器資料 }
}
Container.PENDING = 'pending';
Container.RESOLVED = 'resolved';
Container.REJECTED = 'rejected';
二:Promise容器實體的構造方式(容器托管excutor函式的運行結果狀態和資料)
1.構造方法的引數函式excutor的定義
- 哪里定義:容器外部定義
- 函式職責:1是業務操作載體,2是把excutor運行后的狀態和資料交給容器管理,
容器外部對構造方法呼叫示例:
// 重點關注:構造引數excutor函式的定義,它定義了兩個形參resolve和reject
const p1 = new Container((resolve, reject) => {
// 內部邏輯決定什么時候初始化容器資料,初始化什么資料
setTimeout(() => {
resolve(0)
})
})
2.構造方法的引數函式excutor的呼叫
- 哪里呼叫:容器內部呼叫
- 呼叫時機:構造容器實體時就呼叫
容器內部對構造方法的實作示例:
class Container {
state = undefined;
value = undefined;
reason = undefined;
// 重點關注:構造引數excutor函式的呼叫,它的呼叫時機以及實參傳遞,
constructor(excutor) {
try {
excutor(this.resolve, this.reject);
this.state = Container.PENDING;
} catch (e) {
this.reject(e)
}
}
resolve = value => { // 寫容器資料 }
reject = reason => { // 寫容器資料 }
then(onResolved, onRejected) { // 讀容器資料 }
}
Container.PENDING = 'pending';
Container.RESOLVED = 'resolved';
Container.REJECTED = 'rejected';
三:往Promise容器中寫入資料(resolve、reject函式負責)
1.resolve、reject函式的定義
- 哪里定義:容器內部定義
- 函式職責:把excutor運行后的狀態和資料交給容器管理,
- 不允許更新容器狀態和資料:如果容器狀態不是pending就視為無效通知,啥也不做,
- 處理狀態訂閱的回呼陣列:onResolvedTodoList 和 onRejectedTodoList陣列中存盤著訂閱某一狀態的回呼函式,在初始化好容器管理的狀態和資料后,就可以根據容器狀態,按插入順序把回呼函式從回呼陣列中逐個取出執行,
- 定義在容器內部:定義在容器內部,方便訪問并設定容器狀態和資料,
- 定義為箭頭函式:定義為箭頭函式,呼叫者就不能通過任何方式改變其內部的this指向,即使使用call、apply、bind函式,
容器內部對resolve和reject的實作示例:
class Container {
constructor(excutor) {}
resolve = value => {
if (this.state != Container.PENDING) return
this.status = Container.RESOLVED;
this.value = value;
while (this.onResolvedTodoList.length) this.onResolvedTodoList.shift()() // 取出第一個
}
reject = reason => {
if (this.state != Container.PENDING) return
this.status = Container.REJECTED;
this.reason = reason;
while (this.onRejectedTodoList.length) this.onRejectedTodoList.shift()()
}
then(onResolved, onRejected) {}
}
2.resolve、reject函式的呼叫
- 哪里呼叫:容器外部呼叫
- 呼叫時機:支持同步、異步方式呼叫,異步呼叫時,表現為回呼函式的呼叫,
容器外部對resolve和reject的呼叫示例:
const p1 = new Promise((resolve, reject) => {
// 同步方式往容器中寫入資料
// resolve(0)
// 異步方式往容器中寫入資料
setTimeout(() => {
resolve(1)
})
})
四:從Promise容器中讀取資料(then方法負責)
1.then函式的定義
- 哪里定義:容器內部定義
- 函式職責:接收呼叫者傳入的兩個回呼函式,根據容器存盤的狀態判斷執行哪一個函式,同時把容器存盤的資料作為輸入執行這個函式,
- 可選狀態處理函式:允許呼叫者只針對某一個狀態進行操作,
- pending狀態處理:pending狀態時,容器沒有拿到excutor運行結果的狀態和資料,所以把兩個回呼函式分別放入回呼函式陣列onResolvedTodoList 和 onRejectedTodoList中,等待程式運行得到excutor執行結果的狀態和資料并交付給容器管理之后再執行,也就是容器的resolve和reject方法呼叫之后,
容器內部對then函式的實作示例:
class Container {
constructor(excutor) {}
resolve = value => {}
reject = reason => {}
onResolvedTodoList = [];
onRejectedTodoList = [];
then(onResolved, onRejected) {
onResolved = onResolved ? onResolved : value => value;
onRejected = onRejected ? onRejected : reason => { throw reason };
switch (this.state) {
case Container.PENDING:
this.onResolvedTodoList.push(() => {
try {
const value = onResolved(this.value);
resolve(value);
} catch (e) {
reject(e);
}
});
this.onRejectedTodoList.push(() => {
try {
const value = onRejected(this.reason);
reject(value);
} catch (e) {
reject(e);
}
});
break;
case Container.RESOLVED:
try {
const value = onResolved(this.value);
resolve(value);
} catch (e) {
reject(e);
}
break;
case Container.REJECTED:
try {
const value = onRejected(this.reason);
resolve(value);
} catch (e) {
reject(e);
}
break;
}
}
}
2.then函式的呼叫
- 哪里呼叫:容器外部呼叫
- 呼叫時機:容器實體構造之后,注意:在容器實體構造好之后,異步向容器中寫入狀態和資料之前的這個時間差內,容器內的狀態和資料還沒準備好,
容器外部對then函式的呼叫示例:
p1.then((value) => {
console.log(value)
}, (reason) => {
console.log(reason)
})
五:實作then函式的鏈式呼叫
- then函式不支持鏈式呼叫的缺陷:then函式沒有回傳值,它雖然可以多次呼叫,但它只能根據容器狀態和資料做一步操作,無法實作操作的流水線式處理,如此依然無法解決回呼函式回圈嵌套的問題,
- then函式支持鏈式呼叫的優點:then函式根據回呼函式的執行結果構造回傳一個新的容器,這樣外部就可以對then函式呼叫回傳的容器狀態和資料再做下一步操作,如此反復,就可以實作操作的流水線式處理 并 解決回呼函式嵌套問題,
1.then函式的定義增強
then方法回傳一個容器物件,這個新容器物件管理的狀態和資料由回呼函式的執行結果來決定,
- 如果回呼函式運行回傳一個值,則呼叫resolve方法初始化這個新容器的狀態和資料,
- 如果回呼函式運行回傳一個容器并且不是新容器本身,則呼叫這個回呼函式回傳的容器的then方法來讀取其內部管理的狀態和資料,而后呼叫新容器的resolve或者reject方法來初始化新容器的狀態和資料,這個先讀取后寫入的具體通信程序,是通過把新容器的resolve或者reject方法作為回呼函式回傳容器的下一步操作(也就是分別作為then方法的引數onResolved和onRejected)來實作的,
- 如果回呼函式運行回傳新容器本身,如果不做特殊處理,則會造成讀取新容器的狀態和資料作為新容器的狀態和資料的現象,永遠都不會結束,陷入無限回圈,這個問題的解決方案,就是讓回呼函式的運行(新容器的excutor)作為異步任務放入任務佇列中,這樣做的好處在初始化這個新容器的狀態和資料之前,就可以拿到這個新容器物件進行判斷,
容器內部對then函式支持鏈式呼叫的實作示例:
class Container {
constructor(excutor) {}
resolve = value => {}
reject = reason => {}
onResolvedTodoList = [];
onRejectedTodoList = [];
then(onResolved, onRejected) {
onResolved = onResolved ? onResolved : value => value;
onRejected = onRejected ? onRejected : reason => { throw reason };
let containerBack = new Container((resolve, reject) => {
switch (this.state) {
case Container.PENDING:
this.onResolvedTodoList.push(() => {
setTimeout(() => {
try {
const value = onResolved(this.value);
resolveContainer(containerBack, value, resolve, reject);
} catch (e) {
reject(e);
}
})
});
this.onRejectedTodoList.push(() => {
setTimeout(function () {
try {
const value = onRejected(this.reason);
resolveContainer(containerBack, value, resolve, reject);
} catch (e) {
reject(e);
}
})
});
break;
case Container.RESOLVED:
setTimeout(() => {
try {
const value = onResolved(this.value);
resolveContainer(containerBack, value, resolve, reject);
} catch (e) {
reject(e);
}
})
break;
case Container.REJECTED:
setTimeout(function () {
try {
const value = onRejected(this.reason);
resolveContainer(containerBack, value, resolve, reject);
} catch (e) {
reject(e);
}
})
break;
}
});
return containerBack
}
}
function resolveContainer(containerBack, value, resolve, reject) {
if (!(value instanceof Container)) {
resolve(value)
} else {
if (value === containerBack) {
reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
} else {
value.then(resolve, reject);
}
}
}
2.then函式的鏈式呼叫
- then鏈式呼叫的目的:在呼叫then方法之后,呼叫者可以根據回呼函式的執行結果再做下一步操作,也就是監聽回呼函式運行結果的回呼函式,
容器外部對then函式的鏈式呼叫示例:
const p2 = p1.then((value) => {
console.log(value)
}, (reason) => {
console.log(reason)
})
p2.then((value) => {
console.log('p2', value)
}, (reason) => {
console.log('p2', reason)
}).then((value) => {
console.log('p3', value)
}, (reason) => {
console.log('p3', reason)
})
本文結束,謝謝觀看,
如若認可,一鍵三連,
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/208186.html
標籤:其他
上一篇:2020-11-06
下一篇:jQuery基礎知識
