手寫Promise,經過Promise/A+的872個測試

githubjavascript

博客java

Promise的聲明

當咱們使用Promise的時候,一般都是new Promise((resolve, reject) => {})git

所以咱們能夠看出:github

  • Promise是一個類;
  • Promise類的構造函數的第一個參數是函數,這個函數叫處理器函數(executor function);
  • 而在處理器函數中,有了兩個參數:resolvereject
    • 當異步任務順利完成且返回結果值的時候,咱們會調用resolve函數;
    • 當異步任務失敗且返回失敗緣由(一般是一個錯誤對象)時,會調用reject函數。

所以,咱們能夠初步聲明一下Promise類。shell

class Promise {
    /** * 構造器 * @returns {Promise<object>} * @param executor<function>: executor有兩個參數:resolve和reject */
    constructor(executor) {
        // resolve 成功
        const resolve = () => {};

        // reject 失敗
        const reject = () => {};

        // 執行 executor
        executor(resolve,reject);
    }
}
複製代碼

實現Promise的基本狀態

Promise存在着三種狀態:pending(等待態)、fulfilled(成功態)和rejected(失敗態):數組

  • Promise的初始狀態是pending狀態;
  • pending狀態能夠轉換爲fulfilled狀態和rejected狀態;
  • fulfilled狀態不能夠轉爲其餘狀態,且必須有一個不可改變的值(value);
  • rejected狀態不能夠轉爲其餘狀態,且必須有一個不可改變的緣由(reason);
  • 當在處理器函數中調用resolve函數並傳入參數value,則狀態改變爲fulfilled,且不能夠改變;
  • 當在處理器函數中調用reject函數並傳入參數reason,則狀態改變爲rejected,且不能夠改變;
  • 若處理器函數執行中報錯,直接執行reject函數。

所以,咱們須要在Promise類中設置三個變量:state(狀態變量),value(成功值的變量)和reason(失敗緣由的變量),而後在resolve函數、reject函數以及執行executor函數報錯的時候改變state的值。promise

class Promise {
    constructor(executor) {
        // 初始化狀態
        this.state = 'pending';
        // 成功的值
        this.value = undefined;
        // 失敗的緣由
        this.reason = undefined;
        
        /** * resolve 成功函數 * @param value<any>: 成功的值 */
        const resolve = (value) => {
            // 只能在狀態爲pending的時候執行
            if(this.state === 'pending'){
                // resolve調用後,state轉化爲fulfilled
                this.state = 'fulfilled';
                // 存儲value
                this.value = value;
            }
        };

        /** * reject 失敗函數 * @param reason<any>: 失敗的緣由 */
        const reject = (reason) => {
            // 只能在狀態爲pending的時候執行
            if(this.state === 'pending'){
                // resolve調用後,state轉化爲rejected
                this.state = 'rejected';
                // 存儲reason
                this.reason = reason;
            }
        };

        // 若是executor執行報錯,直接執行reject()
        try {
            executor(resolve,reject);
        }catch (e){
            reject(e);
        }
    }
}
複製代碼

then方法

Promise有一個then方法,而該方法中有兩個參數:onFulfilledonRejectedmarkdown

  • 這兩個參數都是一個函數,且會返回一個結果值;
  • 當狀態爲fulfilled,只執行onFulfilled,傳入this.value
  • 當狀態爲rejected,只執行onRejected,傳入this.reason

所以咱們能夠來實現一下then方法。異步

class Promise {
   constructor(executor) {...}

    /** * then 方法 * @param onFulfilled<function>: 狀態爲fulfilled時調用 * @param onRejected<function>: 狀態爲rejected時調用 */
    then(onFulfilled, onRejected) {
        // 狀態爲fulfilled的時候,執行onFulfilled,並傳入this.value
        if(this.state === 'fulfilled'){
            /** * onFulfilled 方法 * @param value<function>: 成功的結果 */
            onFulfilled(this.value)
        }

        // 狀態爲rejected的時候,onRejected,並傳入this.reason
        if(this.state === 'rejected'){
            /** * onRejected 方法 * @param reason<function>: 失敗的緣由 */
            onRejected(this.reason)
        }
    }
}
複製代碼

異步實現

Promise實際上一個異步操做:async

  • resolve()是在setTimeout內執行的;
  • 當執行then()函數時,若是狀態是pending時,咱們須要等待狀態結束後,才繼續執行,所以此時咱們須要將then()的兩個參數onFulfilledonRejected存起來;
  • 由於一個Promise實例能夠調用屢次then(),所以咱們須要將onFulfilledonRejected各類用數組存起來。

所以咱們能夠藉着完善代碼:

class Promise {
    /** * 構造器 * @returns {Promise<object>} * @param executor<function>: executor有兩個參數:resolve和reject */
    constructor(executor) {
        this.state = 'pending';
        this.value = undefined;
        this.reason = undefined;
        // 存儲onFulfilled的數組
        this.onResolvedCallbacks = [];
        // 存儲onRejected的數組
        this.onRejectedCallbacks = [];

        const resolve = (value) => {
            if (this.state === 'pending') {
                this.state = 'fulfilled';
                this.value = value;
                // 一旦resolve執行,調用onResolvedCallbacks數組的函數
                this.onResolvedCallbacks.forEach(fn => fn());
            }
        };

        const reject = (reason) => {
            if (this.state === 'pending') {
                this.state = 'rejected';
                this.reason = reason;
                // 一旦reject執行,調用onRejectedCallbacks數組的函數
                this.onRejectedCallbacks.forEach(fn=>fn());
            }
        };

        try {
            executor(resolve, reject);
        } catch (e) {
            reject(e);
        }
    }

    then(onFulfilled, onRejected) {
        if (this.state === 'fulfilled') {
            onFulfilled(this.value)
        }

  
        if (this.state === 'rejected') {
            onRejected(this.reason)
        }

        // 狀態爲pending的時候,將onFulfilled、onRejected存入數組
        if (this.state === 'pending') {
            this.onResolvedCallbacks.push(() => {
                onFulfilled(this.value)
            })
            this.onRejectedCallbacks.push(() => {
                onRejected(this.reason)
            })
        }
    }
}
複製代碼

實現鏈式調用

咱們經常會像下面代碼同樣使用Promise

new Promise()
    .then()
    .then()
    .then()
複製代碼

這種方法叫作鏈式調用,一般是用來解決回調地獄(Callback Hell)的,就以下的代碼:

fs.readdir(source, function (err, files) {
  if (err) {
    console.log('Error finding files: ' + err)
  } else {
    files.forEach(function (filename, fileIndex) {
      console.log(filename)
      gm(source + filename).size(function (err, values) {
        if (err) {
          console.log('Error identifying file size: ' + err)
        } else {
          console.log(filename + ' : ' + values)
          aspect = (values.width / values.height)
          widths.forEach(function (width, widthIndex) {
            height = Math.round(width / aspect)
            console.log('resizing ' + filename + 'to ' + height + 'x' + height)
            this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
              if (err) console.log('Error writing file: ' + err)
            })
          }.bind(this))
        }
      })
    })
  }
})
複製代碼

爲了實現鏈式調用,咱們須要知足一下幾點:

  • 咱們須要在then()返回一個新的Promise實例;
  • 若是上一個then()返回了一個值,則這個值就是onFulfilled()或者onRejected()的值,咱們須要把這個值傳遞到下一個then()中。

而對於上一個then()的返回值,咱們須要對齊進行必定的處理,所以封裝一個resolvePromise()的方法去進行判斷處理;

接下來咱們對then()方法進行修改:

class Promise {
    constructor(executor) { ... }

    /** * then 方法 * @returns {Promise<object>} * @param onFulfilled<function>: 狀態爲fulfilled時調用 * @param onRejected<function>: 狀態爲rejected時調用 */
    then(onFulfilled, onRejected) {
        // 返回一個新的Promise實例
        const newPromise = new Promise((resolve, reject) => {

            if (this.state === 'fulfilled') {
                const x = onFulfilled(this.value)

                // 對返回值進行處理 
                resolvePromise(newPromise, x, resolve, reject);
            }

            if (this.state === 'rejected') {
                const x = onRejected(this.reason);

                // 對返回值進行處理 
                resolvePromise(x, resolve, reject);
            }

            if (this.state === 'pending') {
                this.onResolvedCallbacks.push(() => {
                    const x = onFulfilled(this.value);

                    // 對返回值進行處理 
                    resolvePromise(newPromise, x, resolve, reject);
                })
                this.onRejectedCallbacks.push(() => {
                    const x = onRejected(this.reason);

                    // 對返回值進行處理 
                    resolvePromise(newPromise, x, resolve, reject);
                })
            }
        });
      
      	return newPromise;
    }
}

function resolvePromise() {}
複製代碼

完成resolvePromise函數

對於上一個then()的返回值,咱們用x變量存起來,而後須要對它進行一個處理:

  • 判斷x是否是Promise實例;
    • 若是是Promise實例,則取它的結果,做爲新的Promise實例成功的結果;
    • 若是是普通值,直接做爲Promise成功的結果;

而後咱們處理返回值後,須要利用newPromiseresolvereject方法將結果返回。

這裏咱們還須要注意一個地方,就是x等於newPromise的話,這時會形成循環引用,致使死循環。

let p = new Promise(resolve => {
  resolve(0);
});
const p2 = p.then(data => {
  // 循環引用,本身等待本身完成,致使死循環
  return p2;
})
複製代碼

所以,resolvePromise函數須要4個參數,即newPromisexresolvereject

因此咱們來實現一下resolvePromise函數:

/** * resolvePromise 方法 * @param newPromise<object>: 新的Promise實例 * @param x<any>: 上一個then()的返回值 * @param resolve<function>:Promise實例的resolve方法 * @param reject<function>:Promise實例的reject方法 */
function resolvePromise(newPromise, x, resolve, reject) {
    // 循環引用報錯
    if(x === newPromise){
        // reject報錯
        return reject(new TypeError('Chaining cycle detected for promise'));
    }
    // 防止屢次調用
    let called;
    if (x != null && (typeof x === 'object' || typeof x === 'function')) {
        try {
            let then = x.then;
            // x 爲Promise實例
            if (typeof then === 'function') {
                // 使用call執行then(),call的第一個參數是this,後續即then()的參數,即第二個是成功的回調方法,第三個爲失敗的回調函數
                then.call(x, y => {
                    // 成功和失敗只能調用一個
                    if(called)return;
                    called = true;
                    // resolve 的結果依舊是promise實例,那就繼續解析
                    resolvePromise(newPromise, y, resolve, reject);
                }, err => {
                    // 成功和失敗只能調用一個
                    if(called)return;
                    called = true;
                    // 失敗了就直接返回reject報錯
                    reject(err);
                })
            } else {
                // x 爲普通的對象或方法,直接返回
                resolve(x);
            }
        } catch (e) {
            if(called)return;
            called = true;
            reject(e);
        }
    } else {
        // x 爲普通的值,直接返回
        resolve(x);
    }
}
複製代碼

onFulfilledonRejected

關於then()的兩個參數——onFulfilledonRejected

  • 它們都是可選參數,並且它們都是函數,若是不是函數的話,就會被忽略掉;
    • 若是onFulfilled不是一個函數,就將它直接替換成函數value => value
    • 若是onRejected不是一個函數,就將它直接替換成函數err => {throw err};
class Promise {
    constructor(executor) { ... }

    then(onFulfilled, onRejected) {
        // onFulfilled若是不是函數,就忽略onFulfilled,直接返回value
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        // onRejected若是不是函數,就忽略onRejected,直接拋出錯誤
        onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
        
      ...
    }
}
複製代碼

其次,onFulfilledonRejected是不能同步被調用的,必須異步調用。所以咱們就用setTimeout解決一步問題。

class Promise {
    constructor(executor) { ... }

    then(onFulfilled, onRejected) {
        // onFulfilled若是不是函數,就忽略onFulfilled,直接返回value
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        // onRejected若是不是函數,就忽略onRejected,直接拋出錯誤
        onRejected = typeof onRejected === 'function' ? onRejected : err => {
            throw err
        };

        return new Promise((resolve, reject) => {
            if (this.state === 'fulfilled') {
                // 異步調用
                setTimeout(() => {
                    try {
                        const x = onFulfilled(this.value)
                        resolvePromise(x, resolve, reject);
                    }catch (e){
                        reject(e)
                    }
                })
            }

            if (this.state === 'rejected') {
                // 異步調用
                setTimeout(() => {
                    try{
                        const x = onRejected(this.reason);

                        resolvePromise(x, resolve, reject);
                    }catch (e){
                        reject(e)
                    }
                })
            }
          
            if (this.state === 'pending') {
                this.onResolvedCallbacks.push(() => {
                  // 異步調用
                    setTimeout(() => {
                        try {
                            const x = onFulfilled(this.value);
                            resolvePromise(x, resolve, reject);
                        }catch (e){
                            reject(e)
                        }
                    })
                })
                this.onRejectedCallbacks.push(() => {
                  // 異步調用
                    setTimeout(() => {
                        try {
                            const x = onRejected(this.reason);
                            resolvePromise(x, resolve, reject);
                        }catch (e){
                            reject(e)
                        }
                    })
                })
            }
        });
    }
}
複製代碼

實現Promise的其餘方法

Promise.all()

Promise.all()方法接收一個promiseiterable類型的輸入,包括ArrayMapSet。而後返回一個Promise實例,該實例回調返回的結果是一個數組,包含輸入全部promise的回調結果。

但只要任何一個輸入的promisereject回調執行或者輸入不合法的promise,就會立馬拋出錯誤。

/** * Promise.all 方法 * @returns {Promise<object>} * @param promises<iterable>: 一個promise的iterable類型輸入 */
Promise.all = function (promises) {
    let arr = [];

    return new Promise((resolve, reject) => {
       if (!promises.length) resolve([]);
        // 遍歷promises
        for(const promise of promises) {
            promise.then(res => {
                arr.push(res);
                if(arr.length === promises.length){
                    resolve(arr);
                }
            }, reject)
        }
    })
}
複製代碼

Promise.allSettled()

Promise.allSettled()其實跟Promise.all()很像,一樣是接收一個promiseiterable類型的輸入,但返回的是一個給定的promise已經完成後的promise,並帶有一個對象數組,每一個對象標識着對應的promise結果。

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];

Promise.allSettled(promises).
  then((results) => console.log(results));
// > Array [Object { status: "fulfilled", value: 3 }, Object { status: "rejected", reason: "foo" }]
複製代碼

實現:

/** * Promise.allSettled 方法 * @returns {Promise<object>} * @param promises<iterable>: 一個promise的iterable類型輸入 */
Promise.allSettled = function (promises) {
    let arr = [];

    return new Promise((resolve, reject) => {
        try {
            const processData = (data) => {
                arr.push(data);
                if(arr.length === promises.length){
                    resolve(arr);
                }
            }

             if (!promises.length) resolve([]);
            // 遍歷promises
            for(const promise of promises) {
                promise.then(res => {
                    processData({state:'fulfilled', value: res})
                }, err => {
                    processData({state:'rejected', reason: err})
                })
            }
        }catch (e){
            reject(e)
        }
    })
}
複製代碼

Promise.any()

Promise.any()Promise.all()Promise.allSettled()同樣,一樣是接收一個promiseiterable類型的輸入。但只要其中的一個promise成功,就返回那個已經成功的promise,但若是沒有一個promise成功,就返回一個失敗的promise`。

/** * Promise.any 方法 * @returns {Promise<object>} * @param promises<iterable>: 一個promise的iterable類型輸入 */
Promise.any = function (promises) {
    return new Promise((resolve, reject) => {
        // 若是傳入的參數是一個空的可迭代對象,則返回一個 已失敗(already rejected) 狀態的 Promise
        if (!promises.length) reject();
        // 若是傳入的參數不包含任何 promise,則返回一個 異步完成 (asynchronously resolved)的 Promise。
        if (typeof promises[Symbol.iterator] !== 'function' ||
            promises === null ||
            typeof promises === 'string') {
            resolve()
        }

        let i = 0;
        // 遍歷promises
        for (const promise of promises) {
            promise.then(res => {
                i++;
                resolve(res);
            }, err => {
                i++;
                if (i === promises.length) {
                    reject(err);
                }
            })
        }
    })
}
複製代碼

Promise.race()

Promise.race(),一樣是接收一個promiseiterable類型的輸入。一旦迭代器中的某個promise完成了,無論是成功仍是失敗,就會返回這個promise

/** * Promise.race 方法 * @returns {Promise<object>} * @param promises<iterable>: 一個promise的iterable類型輸入 */
Promise.race = function (promises) {
    return new Promise((resolve, reject) => {
        for (const promise of promises) {
            promise.then(resolve, reject)
        }
    })
}
複製代碼

Promise.reject()Promise.resolve()

Promise.reject()方法返回一個帶有拒絕緣由的Promise對象;Promise.resolve()方法返回一個以定值解析後的Promise對象。

/** * Promise.reject 方法 * @returns {Promise<object>} * @param val<any> */
Promise.reject = function (val) {
    return new Promise(reject => reject(val))
}

/** * Promise.resolve 方法 * @returns {Promise<object>} * @param val<any> */
Promise.resolve = function (val) {
    return new Promise(resolve => resolve(val))
}
複製代碼

catch()finally()

catch()方法是用來處理失敗的狀況,它傳入一個處理函數,而後返回一個promise實例。實際上它是then()的語法糖,只接受rejected態的數據。

finally()是在promise結束時,不管結果是fufilled仍是rejected,都會執行指定的回調函數。一樣也返回一個promise實例。

class Promise {
   	constructor(executor) { ... }

    then(onFulfilled, onRejected) { ... }

    /** * catch 方法 * @returns {Promise<object>} * @param callback<function>: 處理函數 */
    catch(callback) {
        return this.then(null, callback);
    }

    /** * finally 方法 * @returns {Promise<object>} * @param callback<function>: 處理函數 */
    finally(callback) {
        return this.then(res => {
            return Promise.resolve(callback()).then(() => res)
        }, err => {
            return Promise.reject(callback()).then(() => {
                throw err
            })
        })
    }
}
複製代碼

Promise/A+測試

Promise/A+規範: github.com/promises-ap…

Promise/A+測試工具: github.com/promises-ap…

安裝promises-aplus-tests插件。

yarn add promises-aplus-tests
複製代碼

Promise.js後面插入下列代碼。

// 測試
Promise.defer = Promise.deferred = function () {
    let dfd = {}
    dfd.promise = new Promise((resolve,reject)=>{
        dfd.resolve = resolve;
        dfd.reject = reject;
    });
    return dfd;
}
module.exports = Promise;
複製代碼

而後輸入命令行進行測試。

promises-aplus-tests Promise.js
複製代碼

結果:

872 passing (18s)
複製代碼
相關文章
相關標籤/搜索