小葵花課堂開課了驚不驚喜,期不期待,今天扯點promise的事情,主要為了學習,最重要的是按照正常人的邏輯器理解認識,promise的架構,不是死記硬背怎麼去實現它
首先提出兩個問題
為什麼要手寫Promise?
Promise解決了什麼問題?
- 回答,因為面試會問,哈哈...,那麼面試為什麼會問...,其實,學習程式設計不是學習配置一堆亂遭的東西,然後去寫程式設計套路,寫程式最重要的是你能心中有自己的一套架構思路,那麼好的架構簡單優雅,要設計好的架構就要從閱讀架構開始,有本兒“賊拉兒”著名的書裡說過:“程式語言是程式設計師表達的方式,而架構是程式設計師對世界的認知”,所以閱讀和重寫Promise這麼經典的東西,學習他的思想和程式設計方式是非常有必要的
- Promise解決了什麼,最最核心的問題就是回撥的問題,在js的非同步操作中,是通過回撥來解決非同步困擾的,回撥多了就成了回撥地獄
舉個例子:
doOne()代表第一件事情,現在,我們想要在這件事情完成後,再做下一件事情doTwo(),應該怎麼做呢? 先看看我們常見的回撥模式。doOne()說:“你要這麼做的話,就把doTwo()交給我,我在結束後幫你呼叫。”所以會是:
doOne(doTwo)
複製程式碼
Promise模式又是如何呢?你對doOne()說:“不行,控制權要在我這裡。你應該改變一下,你先返回一個特別的東西給我,然後我來用這個東西安排下一件事。”這個特別的東西就是Promise,這會變成這樣
doOne().then(doTwo)
複製程式碼
可以看出,Promise將回撥模式從主從關係調換了一個位置,多個事件流程關係就可以集中到主幹道上,而不是分散在各個事件函式之內。 那麼如何做這樣的轉換呢?從最簡單的情況來,假定doOne()程式碼是:
function doOne(callback){
var value =1 ;
callback(value);
}
複製程式碼
那麼他可以改變成
function doOne(){
return {
then :function(callback){
var value = 1 ;
callback(value);
}
}
}
複製程式碼
這就完成了轉換。雖然並不是實際有用的轉換,但到這裡,其實已經觸及了Promise最為重要的實現要點,即Promise將返回值轉換為帶then方法的物件。
無視你犀利的眼神繼續我的表演:好那我們繼續按照這個思路往下來,假設第一件事情是非同步的,那麼在他執行過後,一定會有兩種狀態,要麼成功,要麼失敗,所以這個成功後要做什麼,失敗後要做什麼,這個是動態的是由使用者決定的,所以then裡最好傳兩個函式onFulfilled,onRejected,這兩個函式都是由使用者來定義的,將成果和失敗的結果最引數傳遞給兩個函式由使用者來進行下一步操作,改寫成這個樣子
function doOne(){
return {
then:function(onFulfilled,onRejected){
let value = 0;
setTImeout(function(){
value =1;
},1000)
//這個時候問題來了,我們怎麼知道這個函式到底應該是走onFulfiled還是onRejected
//同時還要給他傳遞響應的值?onFlfiled(value)還是onRejected(err)
}
}
}
複製程式碼
那麼這個時候,把新遇到的問題彙總一下。
- 我們不可能每次在執行方法的時候都在函式裡寫一大堆什麼return,then之類的方法,我們需要一套通用的解決邏輯?
- 我們要通過一個狀態來判定到底是走onFulfilled還是onRejected方法?
- 我們要解決的是非同步問題,那麼在呼叫這套通用邏輯時需要在等待程式碼執行時,做什麼?是把onFulfile和onRejected存起來麼?
好的我們來一個一個解決問題,第一個問題一套通用的邏輯,Promse的辦法在外面包上一層函式,形成個閉包,那麼我們就可以在閉包中寫一些固定的邏輯了
function promise (executor){
excutor();
//excutor是使用者定義的,裡面可以執行doOne
}
複製程式碼
這時候在改寫下doOne和doTwo的例子
function promise (executor){
excutor();
return {
then:function (onFlfiled,onRejected){
...
}
}
}
promise (doOne);
複製程式碼
可以在excutor立即執行函式外面加個try,catch去判定當前的執行狀態,在catch中去執行then的onRejected,程式碼如下:
function promise (executor){
try{
excutor();
} catch(e){
onRejected(e);//我們要的是這個意思,但是這個時候呼叫onRejected是呼叫不到的,所以必須通過一個什麼東西來存取當前的狀態,在呼叫then的時候在走響應的函式,類似於釋出訂閱模式
}
return {
then:function (onFlfiled,onRejected){
}
}
}
複製程式碼
所以then裡要間接的通過一個狀態(釋出訂閱模式的事件)來判斷,是走onFulfiled還是onRejected,那麼這個函式既要維護狀態,還要返回物件,同時物件上還要帶有方法的屬性,我們可以吧promise改寫成建構函式的模式
class Promise{
construct(excutor){
this.state= pending;//非同步執行階段等待狀態
try{
excutor();
}catch(e){
this.state=rejected;
}
}
then(onFulfiled,onRejected){
if(this.state === rejected){
onEjected();
}
...
//這個時候又他麼寫不下去了,onReject應該傳遞個值為e的引數,那麼這個值應該儲存起來,同時合適呼叫onFulfiled是個問題
//所以我們應該給excutor傳個引數,這個引數也是個方法,當在excutor裡面執行完操作後執行這個方法,將狀態置為成功態
//同時要確保狀態只能從等待太變成成功態或者失敗態,切不可逆
}
}
複製程式碼
如程式碼中所言又遇到了問題
此時,問題彙總下:
- then中執行函式,要接受到e的值,所以這個值要存起來
- 所以我們應該給excutor傳個引數,這個引數也是個方法,當在excutor裡面執行完操作後執行這個方法,將狀態置為成功態
- 同時要確保狀態只能從等待太變成成功態或者失敗態,切不可逆
程式碼如下:
class Promise {
construct(){
this.state = "pending";
this.resolveAry=[];
this.rejectAry=[];
try{
excutor(resolve,reject);//加完resolve,應該想到那使用者也應該也能自己把狀態製成失敗態,索性在加個reject
}catch(e){
this.state=rejected;
}
}
resolve(data){
if(this.state === "pending"){//狀態不可逆,通過狀態來判定執行
this.state ="resolve"
this.value = data;
this.resolveAry.forEach((item,index)=>{
item(this.value);
})
}
}
reject(data){
if(this.state ==="pending"){
this.state ="rejected";
this.value = data;
this.rejectAry.forEach((item,index)=>{
item(this.value);
})
}
}
then(onFulfiled,onRejected){
if(this.state === 'rejected'){
onRejected();
}else if (this.state === 'resolve'){
onFulfiled();
}else if (this.state === 'pending'){// 如果在等待態是,將函式都分別存起來,狀態改變後,在去執行
this.resolveAry.push(onFulfiled);
thi.rejectAry.push(onRejected);
}
}
}
複製程式碼
將上面的程式碼拿去編輯器中除錯一下,發現因為roslve,reject雖然在建構函式中定義,但是執行時是使用者同過excutor來執行的,所以this不是原先的this,所以我需要原來的this,這時候當你需要某個作用域的值,最好的方式就是創造個閉包,把this存起來,通過作用域鏈來找到它,程式碼如下:
class Promise {
constructor(excutor){
this.state = "pending";
this.resolveAry=[];
this.rejectAry=[];
this.value =null;
this.self = this;
try{
let resolve = this.resolve();
let reject = this.reject();
excutor(resolve,reject);
}catch(e){
this.state="rejected";
this.value = e;
this.rejectAry.forEach((item,index)=>{
item(e);
})
}
}
resolve (){
let self = this;
return (data)=>{
if(self.state === "pending"){
self.state ="resolve"
self.value = data;
self.resolveAry.forEach((item,index)=>{
item(self.value);
})
}
}
}
reject(){
let self = this;
return (data)=>{
if(self.state ==="pending"){
self.state ="rejected";
self.value = data;
self.rejectAry.forEach((item,index)=>{
item(self.value);
})
}
}
}
then(onFulfiled,onRejected){
if(this.state === 'rejected'){
onRejected(this.value);
}else if (this.state === 'resolve'){
onFulfiled(this.value);
}else if (this.state === 'pending'){
this.resolveAry.push(onFulfiled);
thi.rejectAry.push(onRejected);
}
}
}
複製程式碼
程式碼寫到這,已經非常像原生的promise了,突然想起有個很重要的基礎問題還沒解決
要想實現流程控制,最基礎的就是要做到能夠鏈式呼叫,也就是說then完了,我還特麼能then,你就說皮不皮,一說到鏈式呼叫,我們立馬就會想到this,然而我們稍微在仔細想一想就會發現我們既然是流程控制,每一步做的都不是同一件事或者說不是同一個方法或物件,既然一段方法已經生成了一個狀態,就不可能在有另一個狀態,那麼要實現鏈式,就要返回一個新的promise,它有一個新的then讓你去鏈式一下。
好,那實現起來就簡單多了,上程式碼:(前面濾過直接看then裡的變化)
class Promise {
constructor(excutor){
this.state = "pending";
this.resolveAry=[];
this.rejectAry=[];
this.value =null;
this.self = this;
try{
let resolve = this.resolve();
let reject = this.reject();
excutor(resolve,reject);
}catch(e){
this.state="rejected";
this.value = e;
this.rejectAry.forEach((item,index)=>{
item(e);
})
}
}
resolve (){
let self = this;
return (data)=>{
if(self.state === "pending"){
self.state ="resolve"
self.value = data;
self.resolveAry.forEach((item,index)=>{
item(self.value);
})
}
}
}
reject(){
let self = this;
return (data)=>{
if(self.state ==="pending"){
self.state ="rejected";
self.value = data;
self.rejectAry.forEach((item,index)=>{
item(self.value);
})
}
}
}
then(onFulfiled,onRejected){
let self = this;
let promise2= new Promise((resolve,reject)=>{//從新包裝一層promise,應為promise裡是立即執行函式,所以不會有任何影響,還加了層promise
if(self.state === 'rejected'){
let x = onRejected(self.value);
reject(x);
}else if (self.state === 'resolve'){
let x = onFulfiled(self.value);
resolve(x);
}else if (self.state === 'pending'){//pending狀態時應為要存起來,但是我們還需要當前函式的resolve,包層函式
self.resolveAry.push((data)=>{
let x = onFulfiled(data);
resolve(x);
});
self.rejectAry.push(()=>{
let x = onRejected(data);
resolve(x);
});
}
})
return promise2;
}
}
複製程式碼
寫到這程式碼已經實現了,鏈式呼叫,也就是說promie.then()的返回值可以繼續then下去,實現我們想要的流程控制,但是現在問題又來了:
如果在then中去接著去呼叫priomise或者引用其他人的promise怎麼辦?
是時候要祭出神器了
在promiseA+中為了處理這個問題,規定了一個resolvePromise函式,照著PromiseA+來寫出了下面邏輯resolvePromise=(promise2,x,resolve,reject)=>{
if(promise2 ===x){
return reject(new TypeError('迴圈引用'))
}
if(x!=null&&(typeof x ==='object'||typeof x ==='function')){
try{// 根據PromiseA+規範[promiseA+2.3.3.2]
let then = x.then
if(typeof then ==='function'){//then是和個函式時暫且認為他是promies[promiseA+2.3.3.3]
then.call(x,(y)=>{
resolve(y)
},(e)=>{
reject(e);
})
} else {
resolve(x);
}
}catch(e){
reject(e);
}
}else {//x就是一個普通值,返回promise的成功
resolve(x)
}
}
複製程式碼
promiseA+2.3.3.2 promiseA+2.3.3.3
增加遞迴呼叫,解決多個promise巢狀問題
resolvePromise=(promise2,x,resolve,reject)=>{
if(promise2 ===x){
return reject(new TypeError('迴圈引用'))
}
let called; // 用來防止其他人寫的promise即呼叫resolve,有呼叫reject多次呼叫
if(x!=null&&(typeof x ==='object'||typeof x ==='function')){
try{// 根據PromiseA+規範[promiseA+2.3.3.2]
let then = x.then
if(typeof then ==='function'){//then是和個函式時暫且認為他是promies[promiseA+2.3.3.3]
then.call(x,(y)=>{
if (called) return;
called = true;
//resolve(y)
resolvePromise(promise2,y,resolve,reject);//遞迴呼叫,防止多個promise巢狀
},(e)=>{
if (called) return;
called = true;
reject(e);
})
} else {
resolve(x);
}
}catch(e){
if (called) return;
called = true;
reject(e);
}
}else {//x就是一個普通值,返回promise的成功
resolve(x)
}
}
複製程式碼
這時候程式碼,已經基本與promise一致了,我們發現原生promise其實在then的時候,可以不寫任何東西,會直接穿透,所以在then中加入下面邏輯:
then(onFulfiled,onRejected){
onfulfilled = typeof onfulfilled == 'function' ? onfulfilled : val=>val;
onrejected = typeof onrejected === 'function' ? onrejected :err => {
throw err;
}
let self = this;
let promise2= new Promise((resolve,reject)=>{
if(self.state === 'rejected'){
let x = onRejected(self.value);
resolvePromise(promise2, x, resolve, reject)
}else if (self.state === 'resolve'){
let x = onFulfiled(self.value);
resolvePromise(promise2, x, resolve, reject)
}else if (self.state === 'pending'){
self.resolveAry.push((data)=>{
let x = onFulfiled(data);
resolvePromise(promise2, x, resolve, reject)
});
self.rejectAry.push(()=>{
let x = onRejected(data);
resolvePromise(promise2, x, resolve, reject)
});
}
})
return promise2;
}
複製程式碼
現在還有最後一個小問題,就是在PromiseA+中明確規定promise A+ 2.2.4,then中是非同步的,那麼在原生中用“微任務”來實現的,也就是說在執行完同步程式碼後首先執行的,那麼我們可以用setTimeout(巨集任務)去模擬一下,把它放在巨集任務佇列中,在效果上區別不太大,如果與原生混著用,那可能在執行順序上有點小瑕疵,不過我也沒測試過,有興趣的可以看一看,最後改寫完的程式碼是這個樣子的:
resolvePromise=(promise2,x,resolve,reject)=>{
if(promise2 ===x){
return reject(new TypeError('迴圈引用'))
}
let called; // 用來防止其他人寫的promise即呼叫resolve,有呼叫reject多次呼叫
if(x!=null&&(typeof x ==='object'||typeof x ==='function')){
try{// 根據PromiseA+規範[promiseA+2.3.3.2]
let then = x.then
if(typeof then ==='function'){//then是和個函式時暫且認為他是promies[promiseA+2.3.3.3]
then.call(x,(y)=>{
if (called) return;
called = true;
//resolve(y)
resolvePromise(promise2,y,resolve,reject);//遞迴呼叫,防止多個promise巢狀
},(e)=>{
if (called) return;
called = true;
reject(e);
})
} else {
resolve(x);
}
}catch(e){
if (called) return;
called = true;
reject(e);
}
}else {//x就是一個普通值,返回promise的成功
resolve(x)
}
}
export class Promise {
constructor(excutor){
this.state = "pending";
this.resolveAry=[];
this.rejectAry=[];
this.value =null;
this.self = this;
try{
let resolve = this.resolve();
let reject = this.reject();
excutor(resolve,reject);
}catch(e){
this.state="rejected";
this.value = e;
this.rejectAry.forEach((item,index)=>{
item(e);
})
}
}
resolve (){
let self = this;
return (data)=>{
if(self.state === "pending"){
self.state ="resolve"
self.value = data;
self.resolveAry.forEach((item,index)=>{
item(self.value);
})
}
}
}
reject(){
let self = this;
return (data)=>{
if(self.state ==="pending"){
self.state ="rejected";
self.value = data;
self.rejectAry.forEach((item,index)=>{
item(self.value);
})
}
}
}
then(onFulfiled,onRejected){
onfulfilled = typeof onfulfilled == 'function' ? onfulfilled : val=>val;
onrejected = typeof onrejected === 'function' ? onrejected :err => {
throw err;
}
let self = this;
let promise2= new Promise((resolve,reject)=>{
if(self.state === 'rejected'){
setTimeout(()=>{
try{
let x = onRejected(self.value);
resolvePromise(promise2, x, resolve, reject)
}catch(e){
reject(e);
}
},0)
}else if (self.state === 'resolve'){
setTimeout(()=>{
try{
let x = onFulfiled(self.value);
resolvePromise(promise2, x, resolve, reject)
}catch(e){
reject(e);
}
},0)
}else if (self.state === 'pending'){
self.resolveAry.push((data)=>{
setTimeout(()=>{
try{
let x = onFulfiled(data);
resolvePromise(promise2, x, resolve, reject)
}catch(e){
reject(e);
}
},0)
});
self.rejectAry.push(()=>{
setTimeout(()=>{
try{
let x = onRejected(data);
resolvePromise(promise2, x, resolve, reject)
}catch(e){
reject(e);
}
},0)
});
}
})
return promise2;
}
}
複製程式碼
大工告成,夜已深了,我心疲憊,就不校驗了,有哪寫錯的地方請見諒,最後配個圖樂呵樂呵