非同步程式設計方案----Promise實現小解

槑有人用發表於2018-03-16

Promise作為非同步程式設計方案callback的進階版,解決了callback回撥地域問題的同時,還增加了一些特性:比如all,race方法是的promise在處理非同步程式設計時更加的強大和靈活。

Promise特性

  1. 構造器需要一個引數executor(執行器)函式,executor同時需要resolvereject兩個函式作為引數,resolve是將promise轉換為fulfilled(成功態)時呼叫的函式,reject是將promise轉換為rejected(失敗態)時呼叫的函式。
  2. Promise具有三種狀態:pending(預設態),fulfilledrejected,只能由pending--->fulfilled或者pending--->rejected。fulfilled和rejected之間不能轉換
  3. promise的then函式具有兩個引數onFullfilledonRejected兩個回撥函式,這兩個回撥函式會在promise確定了狀態之後進行對應的呼叫
  4. onFullfilledonRejected兩個回撥函式會被新增進微任務佇列(延時呼叫,不過優先順序高於setTimeout巨集任務)
  5. executor裡面如果有setTimeout延時呼叫,則promise會暫時處於pending狀態,此時執行then函式時需要onFullfilledCallbacksonRejectedCallbacks兩個回撥佇列用於分別快取對應的回撥函式
  6. then函式會返回一個新的promise作為下一步的鏈式呼叫

根據以上特性初步實現一個最初的promise,程式碼如下:

class Promise {
    constructor(executor) {
        this.status = 'pending' //預設狀態
        this.value = undefined; //成功時儲存resolve傳過來的引數
        this.reason = undefined; //失敗時儲存reject傳過來的原因
        this.onFullfilledCallbacks = [];//成功時回撥佇列
        this.onRejectedCallbacks = [];//失敗時回撥佇列

        let resolve = (data) => {
            this.status = 'fullfilled';
            this.value = data;
            this.onFullfilledCallbacks.forEach(fn => fn());
        }

        let reject = (err) => {
            this.status = 'rejected';
            this.reason = err;
            this.onRejectedCallbacks.forEach(fn => fn());
        }

        try {
            executor(resolve, reject);
        } catch (err) {//執行器如果報錯直接轉為失敗態,並且將失敗原因傳出
            reject(err);
        }
    }

    then(onFullfilled, onRejected) {
        let promise2;//返回新的promise

        if(this.status === 'fullfilled') {//如果成功了
            promise2 = new Promise((resolve, reject) => {
                setTimeout(() => {
                    try {
                        let x = onFullfilled(this.value);
                        resolve(x);
                    } catch (err) {//失敗將錯誤傳出
                        reject(err);
                    }
                }, 10);
            });
        }

        if(this.status === 'rejected') {//如果成功了
            promise2 = new Promise((resolve, reject) => {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason);
                        resolve(x);//即使失敗了下一次then依然會走進成功的回撥函式
                    } catch (err) {//失敗將錯誤傳出
                        reject(err);
                    }
                }, 10);
            });
        }

        if(this.status === 'pending') {//pending狀態需要將對應的回撥快取起來
            promise2 = new Promise((resolve, reject) => {
                this.onFullfilledCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFullfilled(this.value);
                            resolve(x);
                        } catch (err) {//失敗將錯誤傳出
                            reject(err);
                        }
                    }, 10);
                });

                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason);
                            resolve(x);//即使失敗了下一次then依然會走進成功的回撥函式
                        } catch (err) {//失敗將錯誤傳出
                            reject(err);
                        }
                    }, 10);
                });
            });
        }

        return promise2;
    }
}
複製程式碼

then鏈式呼叫實現

按照promise A+ 規範,返回值x需要進行以下解析處理:

1. x 與 promise 相等

如果 promise 和 x 指向同一物件,以 TypeError 為據因拒絕執行 promise

2. x 為 Promise

如果 x 為 Promise ,則使 promise 接受 x 的狀態:

  • 如果 x 處於等待態, promise 需保持為等待態直至 x 被執行或拒絕
  • 如果 x 處於執行態,用相同的值執行 promise
  • 如果 x 處於拒絕態,用相同的據因拒絕 promise

3. x 為物件或函式

  • 把 x.then 賦值給 then
  • 如果取 x.then 的值時丟擲錯誤 e ,則以 e 為據因拒絕 promise
  • 如果 then 是函式,將 x 作為函式的作用域 this 呼叫之。傳遞兩個回撥函式作為引數,第一個引數叫做 resolvePromise ,第二個引數叫做 rejectPromise:
    • 如果 resolvePromise 以值 y 為引數被呼叫,則執行 [[Resolve]](promise, y)
    • 如果 rejectPromise 以據因 r 為引數被呼叫,則以據因 r 拒絕 promise
    • 如果 resolvePromise 和 rejectPromise 均被呼叫,或者被同一引數呼叫了多次,則優先採用首次呼叫並忽略剩下的呼叫
    • 如果呼叫 then 方法丟擲了異常 e:
      • 如果 resolvePromise 或 rejectPromise 已經被呼叫,則忽略之
      • 否則以 e 為據因拒絕 promise
    • 如果 then 不是函式,以 x 為引數執行 promise

4. 如果 x 不為物件或者函式

以 x 為引數執行 promise

resolvePromise程式碼實現

function resolvePromise(promise2, x, resolve, reject) {
    let called = false;

    if(promise2 === x) {// x 與 promise 相等
        return reject(new TypeError('迴圈引用了')); //丟擲錯誤
    }

    if(x instanceof Promise) {
        if(x.status === 'pending') {//如果 x 處於等待態
            x.then(function(value) {
                resolvePromise(promise2, value, resolve, reject);// promise 需保持為等待態直至 x 被執行或拒絕
            }, reject);
        } else {//如果 x 處於執行態或者拒絕態
            x.then(resolve, reject);
        }

        return;
    }

    if(x !== null && (typeof x === 'object' || typeof x === 'function')) {//x 為物件或函式
        try {
            let then = x.then;

            if(typeof then === 'function') {//如果 then 是函式
                then.call(x, function(y) {//resolvePromise以值 y 為引數被呼叫
                    if(called) {//如果 resolvePromise 和 rejectPromise 均被呼叫,或者被同一引數呼叫了多次
                        return; //則優先採用首次呼叫並忽略剩下的呼叫
                    }

                    called = true;
                    resolvePromise(promise2, y, resolve, reject);//執行 [[Resolve]](promise, y)
                }, function(r) {//如果 rejectPromise 
                    if(called) {//如果 resolvePromise 和 rejectPromise 均被呼叫,或者被同一引數呼叫了多次
                        return; //則優先採用首次呼叫並忽略剩下的呼叫
                    }
    
                    called = true;
                    reject(r);//以 r 為原因拒絕 promise
                });
            } else {//如果 then 不是函式
                resolve(x);//以 x 為引數執行 promise
            }
        } catch (err) {//如果取 x.then 的值時丟擲錯誤
            if(called) {//如果 resolvePromise 或 rejectPromise 已經被呼叫
                return; //忽略之
            }

            called = true;
            reject(err);//以 e 為原因拒絕 promise
        }
    } else {//如果 x 不為物件或者函式
        resolve(x);//以 x 為引數執行 promise
    }
}
複製程式碼

其他補充

1. Promise的then鏈式呼叫還有個特點,就是在中間then沒有傳入引數時可以依次傳遞資料,例如:

Promise.resolve('aaaa').then().then().then(function(data) {console.log(data)});

這個特性是因為onFullfilled和onRejected在沒有被傳入時會被賦予預設值,如下:

if(typeof onFullfilled !== 'function') {
    onFullfilled = value => value;
}

if(typeof onRejected !== 'function') {
    onRejected = err => {
        throw err;
    }
}
複製程式碼

2. Promise例項的catch方法類似不傳onFullfilled引數的then方法,實現如下:

catch(callback) {
    return this.then(null, callback);
}
複製程式碼

3. Promise的靜態all方法能夠在傳入的所以promise成功後返或資料,其中有一個失敗則全部失敗。

static all(promises) {
    let dataArr = [];
    let len = promises.length;
    let count = 0;//統計成功次數

    return new Promise(function(resolve, reject) {
        for (let i = 0;i < len;i++) {
            p.then(function(data) {
                dataArr[i] = data;
                
                if(++count === len) {//所有promise成功
                    resolve(dataArr);
                }
            }, reject);
        }
    });
}
複製程式碼

4. Promise的靜態race方法使得傳入的所以promise中最先成功的那個結果返回,有一個成功就成功

static race(promises) {
    let len = promises.length;

    return new Promise(function(resolve, reject) {
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(resolve, reject);
        }
    });
}
複製程式碼

完整的Promise實現

class Promise {
    constructor(executor) {
        this.status = 'pending' //預設狀態
        this.value = undefined; //成功時儲存resolve傳過來的引數
        this.reason = undefined; //失敗時儲存reject傳過來的原因
        this.onFullfilledCallbacks = [];//成功時回撥佇列
        this.onRejectedCallbacks = [];//失敗時回撥佇列

        let resolve = (data) => {
            this.status = 'fullfilled';
            this.value = data;
            this.onFullfilledCallbacks.forEach(fn => fn());
        }

        let reject = (err) => {
            this.status = 'rejected';
            this.reason = err;
            this.onRejectedCallbacks.forEach(fn => fn());
        }

        try {
            executor(resolve, reject);
        } catch (err) {//執行器如果報錯直接轉為失敗態,並且將失敗原因傳出
            reject(err);
        }
    }

    then(onFullfilled, onRejected) {
        let promise2;//返回新的promise

        if(typeof onFullfilled !== 'function') {
            onFullfilled = value => value;
        }

        if(typeof onRejected !== 'function') {
            onRejected = err => {
                throw err;
            }
        }

        if(this.status === 'fullfilled') {//如果成功了
            promise2 = new Promise((resolve, reject) => {
                setTimeout(() => {
                    try {
                        let x = onFullfilled(this.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (err) {//失敗將錯誤傳出
                        reject(err);
                    }
                }, 10);
            });
        }

        if(this.status === 'rejected') {//如果成功了
            promise2 = new Promise((resolve, reject) => {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason);
                        resolvePromise(promise2, x, resolve, reject);//即使失敗了下一次then依然會走進成功的回撥函式
                    } catch (err) {//失敗將錯誤傳出
                        reject(err);
                    }
                }, 10);
            });
        }

        if(this.status === 'pending') {//pending狀態需要將對應的回撥快取起來
            promise2 = new Promise((resolve, reject) => {
                this.onFullfilledCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFullfilled(this.value);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (err) {//失敗將錯誤傳出
                            reject(err);
                        }
                    }, 10);
                });

                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason);
                            resolvePromise(promise2, x, resolve, reject);//即使失敗了下一次then依然會走進成功的回撥函式
                        } catch (err) {//失敗將錯誤傳出
                            reject(err);
                        } 
                    }, 10);
                });
            });
        }

        return promise2;
    }

    catch(callback) {
        return this.then(null, callback);
    }

    static all(promises) {
        let dataArr = [];
        let len = promises.length;
        let count = 0;//統計成功次數

        return new Promise(function(resolve, reject) {
            for (let i = 0;i < len;i++) {
                p.then(function(data) {
                    dataArr[i] = data;
                    
                    if(++count === len) {//所有promise成功
                        resolve(dataArr);
                    }
                }, reject);
            }
        });
    }

    static race(promises) {
        let len = promises.length;

        return new Promise(function(resolve, reject) {
            for (let i = 0; i < promises.length; i++) {
                promises[i].then(resolve, reject);
            }
        });
    }

    static deferred() {
        let dfd = {};

        dfd.promise = new Promise(function (resolve, reject) {
            dfd.resolve = resolve;
            dfd.reject = reject;
        });

        return dfd;
    }
}

function resolvePromise(promise2, x, resolve, reject) {
    let called = false;

    if(promise2 === x) {// x 與 promise 相等
        return reject(new TypeError('迴圈引用了')); //丟擲錯誤
    }

    if(x instanceof Promise) {
        if(x.status === 'pending') {//如果 x 處於等待態
            x.then(function(value) {
                resolvePromise(promise2, value, resolve, reject);// promise 需保持為等待態直至 x 被執行或拒絕
            }, reject);
        } else {//如果 x 處於執行態或者拒絕態
            x.then(resolve, reject);
        }

        return;
    }

    if(x !== null && (typeof x === 'object' || typeof x === 'function')) {//x 為物件或函式
        try {
            let then = x.then;

            if(typeof then === 'function') {//如果 then 是函式
                then.call(x, function(y) {//resolvePromise以值 y 為引數被呼叫
                    if(called) {//如果 resolvePromise 和 rejectPromise 均被呼叫,或者被同一引數呼叫了多次
                        return; //則優先採用首次呼叫並忽略剩下的呼叫
                    }

                    called = true;
                    resolvePromise(promise2, y, resolve, reject);//執行 [[Resolve]](promise, y)
                }, function(r) {//如果 rejectPromise 
                    if(called) {//如果 resolvePromise 和 rejectPromise 均被呼叫,或者被同一引數呼叫了多次
                        return; //則優先採用首次呼叫並忽略剩下的呼叫
                    }
    
                    called = true;
                    reject(r);//以 r 為原因拒絕 promise
                });
            } else {//如果 then 不是函式
                resolve(x);//以 x 為引數執行 promise
            }
        } catch (err) {//如果取 x.then 的值時丟擲錯誤
            if(called) {//如果 resolvePromise 或 rejectPromise 已經被呼叫
                return; //忽略之
            }

            called = true;
            reject(err);//以 e 為原因拒絕 promise
        }
    } else {//如果 x 不為物件或者函式
        resolve(x);//以 x 為引數執行 promise
    }
}
複製程式碼

相關文章