Hey, 你的Promise

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的時候:promise

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

複製代碼

再次運行,發現結果和咱們以前想的是同樣的,會依次輸出 1,3,2 這是由於promisecallback是當即執行的,只有then方法是異步的bash

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 是異步的呢?ui

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

運行以後咱們發現什麼都沒有輸出,onFulfilledonRejected 都沒有打印東西,那說明他們都沒有執行,let me think think...this

這是由於若是咱們的 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);
        }
    }
    
    複製代碼
相關文章
相關標籤/搜索