Hey, 你的Promise

asdff發表於2018-07-27

Promise 是非同步程式設計的一種解決方案,比傳統的解決方案——回撥函式和事件——更合理和更強大。它由社群最早提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise物件。 @阮老師

先來看看promise的幾種用法:

  • Promise.prototype.then:then方法有兩個引數,第一個引數是Promise成功時執行的callback,第二個是Promise失敗時執行的callback,then方法返回的是一個新的Promise例項
    new Promise((resolve, reject) => resolve()).then(() => {
        console.log('success');
    }, () => {
        console.log('err')
    })
複製程式碼
  • Promise.prototype.catch:用於指定發生錯誤時的回撥函式
如果沒有指定reject函式,最後就會執行catch函式

new Promise((resolve, reject) => {
    reject('err');
})
.then()
.catch(e => {
    console.log(e); // => Error: err
})
複製程式碼
  • Promise.resolve:定義在Promise類本身的方法,可以通過Promise.resolve()呼叫,相當於直接將狀態改為成功態
Promise.resolve('hahaha').then(data => {
    console.log(data); // => 'hahaha'
})
複製程式碼
  • Promise.reject:定義在Promise類本身的方法,可以通過Promise.reject()呼叫,相當於直接將狀態改為失敗
Promise.reject('hahaha').then(data => {
    console.log(data); // => 'hahaha'
})
複製程式碼
  • Promise.prototype.all:將多個Promise執行的結果放到一個陣列中返回
let promise1 = new Promise((resolve, reject) => {
    setTimeout(() => {
          resolve('promise1');
    }, 1500);
})

let promise2 = new Promise((resolve, reject) => {
    setTimeout(() => {
          resolve('promise2');
    }, 2000);
})

Promise.all([promise1, promise2]).then(data => {
    console.log(data); // => ["promise1", "promise2"]
})
複製程式碼
  • ...

下面我們自己來實現一個符合 PromiseA+規範 的Promise

我們先來簡單的用一下promise:

console.log(1)
new Promise(() => {
    console.log(2)
});
console.log(3)
複製程式碼

因為我們都知道promise是非同步的,所以按理會依次輸出 1,3,2 然後我們執行之後發現 它依次輸出的是 1,2,3 當我們使用then的時候:

console.log(1)
Promise.resolve().then(() => {
    console.log(2);
})
console.log(3)

複製程式碼

再次執行,發現結果和我們之前想的是一樣的,會依次輸出 1,3,2 這是因為promisecallback是立即執行的,只有then方法是非同步的

A promise must be in one of three states: pending, fulfilled, or rejected.

  1. promise必須有三個狀態 pending(等待態) fulfilled(成功態) rejected(失敗態)
    • 當 state 是 pending 的時候
      • 可以將當前狀態改變成其他狀態(fulfilled or rejected)
    • 當 state 是 fulfilled 的時候
      • 不能轉換成其他狀態
      • 必須有一個value(成功之後的值)
    • 當 state 是 rejected 的時候
      • 不能轉換成其他狀態
      • 必須有一個reason(失敗的原因)
class Promise {
    constructor(executor) {
        // 每一個promise例項都有自己的三個狀態,我們用一個變數來儲存當前的狀態
        this.status = 'pending';
        // 用來儲存執行成功時的值
        this.value;
        // 用來儲存執行失敗時的原因
        this.reason;
        
        // 用來將當前狀態改變為成功態的方法
        let resolve = val => {
            // 只有當前狀態是`pending` 才可以改變狀態和值
            if (this.status === 'pending') {
                this.value = val;
                this.status = 'fulfilled';
            }
        }
        
        // 用來將當前狀態改變為失敗態的方法
        let reject = reason => {
            // 只有當前狀態是`pending` 才可以改變狀態和值
            if (this.status === 'pending') {
                this.reason = reason;
                this.status = 'rejected';
            }
        }
        
        // 執行executor可能會直接丟擲異常,我們用try catch包起來
        try {
           executor(resolve, reject); 
        } catch (e) {
            // 如果丟擲異常,我們直接將狀態改為失敗態
            reject(e);
        }
    }
}
複製程式碼
  1. then方法

一個promise必須提供一個then方法去獲取當前的或最終的value or reason

promise的then方法有兩個引數,分別是成功時執行的方法onFulfilled和失敗時執行的方法onRejected

  • onFulfilledonRejected都是可選的引數
    • 如果沒傳,我們需要設定一個預設方法
  • 如果onFulfilled是一個function
    • 它必須在當前promise的狀態變成fulfilled之後被呼叫,將promise的value(成功之後的值)作為他的第一個引數
    • 在狀態是fulfilled之前不能呼叫它
    • 只能被呼叫一次
  • 如果onRejected是一個function
    • 它必須在當前promise的狀態變成rejected之後被呼叫,將promise的reason(失敗的原因)作為他的第一個引數
    • 在狀態是rejected之前不能呼叫它
    • 只能被呼叫一次
promise.then(onFulfilled, onRejected)
複製程式碼
class Promise {
    constructor(executor) {...}
    
    then(onFulfilled, onRejected) {
        
        // 如果當前狀態是成功態,我們就執行成功的回撥,並將存起來的成功的值value傳過去
        if (this.status === 'fulfilled') {
            onFulfilled(this.value);
        }
        
        // 如果當前狀態是失敗態,我們就執行失敗的回撥,並將存起來的失敗原因reason傳過去
        if (this.status === 'rejected') {
            onRejected(this.reason);
        }
    }
}
複製程式碼

好了,我們現在已經可以簡單的測試一下了

new Promise((resolve, reject) => {
    resolve('完美');
}).then(data => {
    console.log(data); // => 完美
})
複製程式碼

完美,但是那麼問題來了,如果我們的 resolvereject 是非同步的呢?

new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('完美');
    }, 1000);
}).then(data => {
    console.log(data); 
}, err => {
    console.log(err);
})
複製程式碼

執行之後我們發現什麼都沒有輸出,onFulfilledonRejected 都沒有列印東西,那說明他們都沒有執行,let me think think...

這是因為如果我們的 resolvereject是非同步的,當我們的then執行的時候,狀態還沒有改變,還是pending狀態,所以當然什麼都不會執行。我們可以先把 onFulfilledonRejected 存起來,等狀態改變的時候依次執行對應的callback。A: 這難道是?B: 沒錯,這就是訂閱釋出模式。下面我們來改寫一下:

class Promise {
    constructor(executor) {
        this.status = 'pending';
        this.value;
        this.reason;
        
        // 用來儲存成功時執行的回撥函式
        this.onSuccessCallback = [];
        
        // 用來儲存失敗時執行的回撥函式
        this.onErrorCallback = [];
        
        let resolve = val => {
            if (this.status === 'pending') {
                this.value = val;
                this.status = 'fulfilled';
                
                // 狀態改變時 依次執行成功的回撥
                this.onSuccessCallback.forEach(fn => fn());
            }
        }
        
        let reject = reason => {
            if (this.status === 'pending') {
                this.reason = reason;
                this.status = 'rejected';
                
                // 狀態改變時 依次執行失敗的回撥
                this.onErrorCallback.forEach(fn => fn());
            }
        }
        
        // 執行executor可能會直接丟擲異常,我們用try catch包起來
        try {
           executor(resolve, reject); 
        } catch (e) {
            // 如果丟擲異常,我們直接將狀態改為失敗態
            reject(e);
        }
    }
    
    then(onFulfilled, onRejected) {
        
        if (this.status === 'fulfilled') {
            onFulfilled(this.value);
        }
        
        if (this.status === 'rejected') {
            onRejected(this.reason);
        }
        
        if (this.status === 'pending') {
        
            // 將成功回撥和失敗回撥都存起來,等待狀態改變,再依次執行對應的方法
            this.onSuccessCallback.push(() => {
                onFulfilled(this.value);
            });
            
            this.onErrorCallback.push(() => {
                onRejected(this.reason);
            });
        }
    }
}
複製程式碼
  • onFulfilledonRejected都是非同步呼叫(微任務)

  • onFulfilledonRejected必須作為一個函式被執行

  • then方法必須返回一個promise

    promise2 = promise1.then(onFulfilled, onRejected)
    複製程式碼
    • 如果onFulFilled or onRejected返回了一個值x,執行promise解析程式
    • 如果onFulfilled or onRejected丟擲了一個異常epromise2的狀態必須是rejected,並將e作為onRejected的引數
    • 如果onFulfilled不是一個function並且promise1fulfilledpromise2必須也是fulfilled並且使用和promise1相同的value
    • 如果onRejected不是一個function並且promise1rejectedpromise2必須也是rejected並且使用和promise1相同的reason

    我們用promise的時候經常promise.then().then().then() ...這種寫法叫鏈式呼叫,那怎麼樣才能繼續呼叫then方法呢,規範規定then方法必須返回一個promise例項,這樣就可以實現鏈式呼叫了

    class Promise {
        contructor() {...}
        
        then(onFulfilled, onRejected) {
            // 如果onFulfilled和onFulfilled 不是一個函式,我們給一個預設值
            onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
            onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
            // 我們將所有東西都包到一個promise例項中,最後返回這個例項,這樣就可以實現鏈式呼叫
            let promise2;
            // `onFulfilled` 和 `onRejected`都是非同步呼叫,我們先用一個定時器實現非同步呼叫
            promise2 = new Promise((resolve, reject) => {
    
                if (this.status === 'fulfilled') {
                    setTimeout(() => {
                        try {
                            // 有一個返回值x,執行解析函式resolvePromise
                            let x = onFulfilled(this.value);
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                }
                    
                if (this.status === 'rejected') {
                    setTimeout(() => {
                        try {
                            // 有一個返回值x,執行解析函式resolvePromise
                            let x = onRejected(this.reason);;
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                }
            
                if (this.status === 'pending') {
        
                    // 將成功回撥和失敗回撥都存起來,等待狀態改變,再依次執行對應的方法
                    this.onSuccessCallback.push(() => {
                        setTimeout(() => {
                            try {
                                // 有一個返回值x,執行解析函式resolvePromise
                                let x = onFulfilled(this.value);
                                resolvePromise(promise2, x, resolve, reject)
                            } catch (e) {
                                reject(e);
                            }
                        }, 0);
                    });
                
                    this.onErrorCallback.push(() => {
                        setTimeout(() => {
                            try {
                                // 有一個返回值x,執行解析函式resolvePromise
                                let x = onRejected(this.reason);;
                                resolvePromise(promise2, x, resolve, reject)
                            } catch (e) {
                                reject(e);
                            }
                        }, 0);
                    });
                }
            });
            return promise2;
        }
    }
    複製程式碼

    這裡可能就會有疑惑了,如果有一個返回值x就執行promise解析程式resolvePromise,這是什麼鬼?

    我們先來看看規範:

    promise的解析程式是將promise2x作為引數的函式

  • 如果promise2x是一個promise,那麼丟擲一個TypeError

  • 如果x是一個objectfunction

    • 使用變數then儲存x.then
    • 如果x.then會丟擲異常e,呼叫promise的reject,並將e作為它的引數
    • 如果then是一個function,使用call把它的this指向x,它的第一個引數是resolvePromise,第二個引數是rejectPromise:
      • resolvePromisey值呼叫的時候,繼續執行解析程式
      • rejectPromise執行的時候,呼叫promise的reject並將將失敗原因r作為它的引數
    • 如果then不是一個object or function,呼叫promise的resolve並將x作為它的引數
  • 如果x不是一個object or function,呼叫promise的resolve並將x作為它的引數

總結下來就兩點:

  1. 如果promise2x相等,就丟擲一個TypeError,我們先來看一下

    let p = new Promise((resolve, reject) => {
        // 返回當前promise例項
        return p;
    });
    
    p.then(data => {
        console.log(data);
    }, err => {
        console.log(err);
    });
    複製程式碼

    執行上面程式碼,我們會發現promise丟擲了一個異常,他告訴我們TypeError: Chaining cycle detected for promise,這是因為p的成功還是失敗取決於自己,自己再等待自己的執行結果,所以他既不會成功也不會失敗

    1. 如果onFulFilled or onRejected返回了一個值x,執行promise解析程式resolvePromise

    返回值x有可能是一個常量,物件,也有可能是一個promise,這個程式的作用就是如果 x 是一個promise,那就將 x 一直解析到常量位置

    let p = new Promise((resolve, reject) => {
        resolve(new Promise((resolve, reject) => {
            resolve(1111);
        }))
    })
    複製程式碼
    let resolvePromise = (promise2, x, resolve, reject) => {
        // 如果promise2和x相等,就丟擲一個型別錯誤
        if (promise2 === x) {
            return reject(new TypeError('錯了'));
        }
        // 只允許呼叫一次resolvePromise
        let called;
        // 如果x不是一個常量繼續解析
        if (x !== null && (typeof x === 'function' || typeof x === 'object')) {
            // 呼叫x.then的時候可能會報錯,因為我們有可能和別人的Promise庫混用 
            try {
                let then = x.then;
                
                // 如果then是一個函式,證明x是一個promise,繼續解析
                if (typeof then === 'function') {
                    then.call(x, y => {
                        if (called) {
                            return;
                        } else {
                            called = true;
                        }
                        resolvePromise(promise2, y, resolve, reject);
                    }, r => {
                        if (called) {
                            return;
                        } else {
                            called = true;
                        }
                        reject(r);
                    })
                } else {
                    // 說明x可能是一個普通物件,不是一個promise
                    resolve(x);
                }
            } catch (e) {
                if (called) {
                    return;
                } else {
                    called = true;
                }
                reject(e);
            }
        } else {
            // 說明x是一個常量,直接執行resolve
            resolve(x);
        }
    }
    複製程式碼

    接下來我們來實現Promise.resolve Promise.reject Promise.all

    class Promise {
        contructor {...}
        then() {...}
        
        // 其實Promise.reject和Promise.reject非常簡單
        static resolve(value) {
            return new Promise((resolve) => {
                resolve(value);
            })
        }
        static reject(reason) {
            return new Promise((resolve, reject) => {
                reject(reason);
            })
        }
        
        // all方法
        static all(promises) {
            return new Promise((resolve, reject) => {
                // 用來儲存結果
                let arr = [];
                let index = 0;
                
                const saveData = (i, data) => {
                    arr[i] = data;
                    if (++index === promises.length) {
                        resolve(arr);
                    }
                }
                
                for (let i = 0; i < promises.length; i++) {
                
                    promises[i].then(data => {
                        saveData(i, data);
                    }, reject)
                }
            })
        }
    }
    複製程式碼

    好了,接下來我們來一個完整版的promise

    class Promise {
        contructor() {
            this.status = 'pending';
            this.value;
            this.reason;
        
            // 用來儲存成功時執行的回撥函式
            this.onSuccessCallback = [];
            
            // 用來儲存失敗時執行的回撥函式
            this.onErrorCallback = [];
        
            let resolve = val => {
                if (this.status === 'pending') {
                    this.value = val;
                    this.status = 'fulfilled';
                    
                    // 狀態改變時 依次執行成功的回撥
                    this.onSuccessCallback.forEach(fn => fn());
                }
            }
        
            let reject = reason => {
                if (this.status === 'pending') {
                    this.reason = reason;
                    this.status = 'rejected';
                    
                    // 狀態改變時 依次執行失敗的回撥
                    this.onErrorCallback.forEach(fn => fn());
                }
            }
        
            // 執行executor可能會直接丟擲異常,我們用try catch包起來
            try {
                executor(resolve, reject); 
            } catch (e) {
                // 如果丟擲異常,我們直接將狀態改為失敗態
                reject(e);
            }
        }
        
        static resolve(value) {
            return new Promise((resolve) => {
                resolve(value);
            })
        }
        static reject(reason) {
            return new Promise((resolve, reject) => {
                reject(reason);
            })
        }
        
        static all(promises) {
            return new Promise((resolve, reject) => {
                // 用來儲存結果
                let arr = [];
                let index = 0;
                
                const saveData = (i, data) => {
                    arr[i] = data;
                    if (++index === promises.length) {
                        resolve(arr);
                    }
                }
                
                for (let i = 0; i < promises.length; i++) {
                
                    promises[i].then(data => {
                        saveData(i, data);
                    }, reject)
                }
            })
        }
        
        then(onFulfilled, onRejected) {
            // 如果onFulfilled和onFulfilled 不是一個函式,我們給一個預設值
            onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
            onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
            // 我們將所有東西都包到一個promise例項中,最後返回這個例項,這樣就可以實現鏈式呼叫
            let promise2;
            // `onFulfilled` 和 `onRejected`都是非同步呼叫,我們先用一個定時器實現非同步呼叫
            promise2 = new Promise((resolve, reject) => {
    
                if (this.status === 'fulfilled') {
                    setTimeout(() => {
                        try {
                            // 有一個返回值x,執行解析函式resolvePromise
                            let x = onFulfilled(this.value);
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                }
                    
                if (this.status === 'rejected') {
                    setTimeout(() => {
                        try {
                            // 有一個返回值x,執行解析函式resolvePromise
                            let x = onRejected(this.reason);;
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                }
            
                if (this.status === 'pending') {
        
                    // 將成功回撥和失敗回撥都存起來,等待狀態改變,再依次執行對應的方法
                    this.onSuccessCallback.push(() => {
                        setTimeout(() => {
                            try {
                                // 有一個返回值x,執行解析函式resolvePromise
                                let x = onFulfilled(this.value);
                                resolvePromise(promise2, x, resolve, reject)
                            } catch (e) {
                                reject(e);
                            }
                        }, 0);
                    });
                
                    this.onErrorCallback.push(() => {
                        setTimeout(() => {
                            try {
                                // 有一個返回值x,執行解析函式resolvePromise
                                let x = onRejected(this.reason);;
                                resolvePromise(promise2, x, resolve, reject)
                            } catch (e) {
                                reject(e);
                            }
                        }, 0);
                    });
                }
            });
            return promise2;
        }
    }
    
    let resolvePromise = (promise2, x, resolve, reject) => {
        // 如果promise2和x相等,就丟擲一個型別錯誤
        if (promise2 === x) {
            return reject(new TypeError('錯了'));
        }
        // 只允許呼叫一次resolvePromise
        let called;
        // 如果x不是一個常量繼續解析
        if (x !== null && (typeof x === 'function' || typeof x === 'object')) {
            // 呼叫x.then的時候可能會報錯,因為我們有可能和別人的Promise庫混用 
            try {
                let then = x.then;
                
                // 如果then是一個函式,證明x是一個promise,繼續解析
                if (typeof then === 'function') {
                    then.call(x, y => {
                        if (called) {
                            return;
                        } else {
                            called = true;
                        }
                        resolvePromise(promise2, y, resolve, reject);
                    }, r => {
                        if (called) {
                            return;
                        } else {
                            called = true;
                        }
                        reject(r);
                    })
                } else {
                    // 說明x可能是一個普通物件,不是一個promise
                    resolve(x);
                }
            } catch (e) {
                if (called) {
                    return;
                } else {
                    called = true;
                }
                reject(e);
            }
        } else {
            // 說明x是一個常量,直接執行resolve
            resolve(x);
        }
    }
    
    複製程式碼

相關文章