ES6-promise學習及手寫promise

image

1. promise要解決的問題:

腦筋急轉彎:把牛關進冰箱里,要分幾步?

// 第一步,打開冰箱
function open(){
    setTimeout(()=>{
        console.log('打開冰箱');
        return 'success';
    }, 1000)
}

// 第二步,放牛進去
function settle(){
      setTimeout(()=>{
       console.log('放牛進去');
       return 'success';
    }, 3000)
}

// 第三步,關上冰箱
function close(){
      setTimeout(()=>{
       console.log('關上冰箱');
       return 'success';
    }, 1000)
}

function closeCow(){
    open();
    settle();
    close()
}

closeCow();

//"打開冰箱"
//"關上冰箱"?
//"放牛進去"?

很顯然,這三個操作不能顛倒順序,否則任務就會失敗。但是上述邏輯并不能保證最終是按照我們想要的順序進行,顯然,難點在于第二步,花費的時間更長。為了保證第二步在第一步執行成功之后再執行,第三步在第二步執行成功之后在執行,改進:

function closeCow() {
    setTimeout(() => {
        console.log("打開冰箱");
        setTimeout(() => {
            console.log("放牛進去");
            setTimeout(() => {
                console.log("關閉冰箱");
            }, 1000);
        }, 3000);
    }, 1000);
}

這樣的確解決了問題,但是看起來很別扭,邏輯不清晰,這就是經典的“回調地獄”。在過去,要想做多重的異步操作,會導致經典的回調地獄,promise的出現,就是為了解決這個問題的。

Promise對象用于表示一個異步操作的最終完成 (或失敗), 及其結果值。

let closeCow = new Promise((resolve, reject) => {
    console.log('把牛放進冰箱');
    resolve();
});
closeCow
    .then(open())   // 打開冰箱
    .then(settle()) // 放牛進去
    .then(close()); // 關上冰箱

這就是promise最簡單的應用。

2. promise深入了解

2.1 Promise狀態

  • Pending(待處理): promise初始化的狀態,正在運行,既沒有完成也沒有失敗的狀態,此狀態可以提升為fulfilledrejected狀態

  • Fulfilled(已完成): 如果回調函數實現Promise的resolve(回調函數),那么state變為fulfilled

  • Rejected(已拒絕): 如果Promise調用過程中遭到拒絕或發生異常,state就會處于rejected狀態。

  • Settled(不變的): Promise從pending狀態提升后,狀態要么變為fulfilled,要么變為rejected,沒有其他情況。

    Promise 的狀態一旦改變,就永久保持該狀態,不會再變了。

2.2 promise實例方法

所謂實例方法(instance method)就是必須實例化之后才能調用的方法,在JS中表現為使用new關鍵字實例化之后才能調用的,是定義在原型上的方法,即Promise.prototype.methodName;

  • Promise.prototype.then(onFulfilled, onRejected): 用來注冊當狀態變為fulfilled或者reject時的回調函數

    // onFulfilled 是用來接收promise成功的值
    // onRejected 是用來接收promise失敗的原因
    promise.then(onFulfilled, onRejected);
    

    注意,then方法是異步執行的,onFulfilledonRejected方法只會執行其中一個,因為promise狀態是單向變化的,要么fulfilled、要么rejected:

    案例一:

    const promise = new Promise((resolve, reject) => {
        resolve('fulfilled...'); // 狀態由 pending --> fulfilled
    });
    
    promise.then(res => {
        console.log(res); // 只會調用 onFulfilled
    }, err => {
        console.log(err); // 不會調用 rejected
    })
    // fulfilled
    

    案例二:

    const promise = new Promise((resolve, reject) => {
        reject('rejected...'); // 狀態由 pending --> rejected
    });
    
    promise.then(res => {
        console.log(res); // 不會調用 onFulfilled
    }, err => {
        console.log(err); // 只會調用 rejected
    })
    // rejected
    

    案例三:

    const promise1 = new Promise((resolve, reject) => {
        resolve('fulfilled...'); // 狀態先由 pending --> rejected
        reject('rejected...'); //  狀態不會再變 pending --> rejected
    });
    
    promise1.then(res => {
        console.log(res); // 只會調用 onFulfilled
    }, err => {
        console.log(err); // 不會調用 rejected
    })
    // fulfilled
    
    const promise2 = new Promise((resolve, reject) => {
        reject('rejected...'); // 狀態先由 pending --> rejected
        resolve('fulfilled...'); // 狀態不會再變 pending --> rejected
    });
    
    promise2.then(res => {
        console.log(res); // 不會調用 onFulfilled
    }, err => {
        console.log(err); // 只會調用 rejected
    })
    // rejected
    

    通過以上三個案例,我們發現,promise狀態一旦由pending提升為fulfilled或rejected就不會再改變了,只能單向變化;并且then方法中只會調用其中一個方法(成功的回調或者事變的回調),不會二者都調用。

    Promise.resolve(1)
      .then(2)
      .then(Promise.resolve(3))
      .then(console.log)
    
    // 輸出 1
    

    Promise的then方法的參數期望是函數,傳入非函數則會發生值穿透

  • Promise.prototype.catch(onRejected): catch在鏈式寫法中可以捕獲前面then中發送的異常

    promise.then(res => {
       console.log('haha');
       return 'haha'
    }).then(res => {
        throw new Error('hehe'); // 此處錯誤被捕獲
    }).catch(err => {
        console.log(err)
    })
    
    promise.then(res => {
        console.log('haha');
        throw new Error('haha'); // 此處錯誤被捕獲
    }).then(res => {
        console.log('hehe');
        throw new Error('hehe'); // 此處不會執行
    }).catch(err => {
        console.log(err)
    })
    

    catch一旦捕獲到了一個then中的錯誤,后續的then方法就不會再執行下去了。其實,catch相當于then(null,onRejected),前者只是后者的語法糖而已,使用catch方法替代更好吧。

  • Promise.prototype.finally(onFinally): 無論當前promise的狀態是完成(fulfilled)還是失敗(rejected)都會執行的回調,并且返回新的promise對象。

    promise.then(res => {
        throw new Error('test');
    }).catch(errr => {
        console.log(err);
    }).finally(()=>{
        // 返回狀態為(resolved 或 rejected)都會執行
        console.log('當前promise執行結束')
    });
    
    let isLoading = true;
    fetch(myRequest).then(function(response) {
        let contentType = response.headers.get("content-type");
        if(contentType && contentType.includes("application/json")) {
          return response.json();
        }
        throw new TypeError("Oops, we haven't got JSON!");
      })
      .then(function(json) { /* process your JSON further */ })
      .catch(function(error) { console.log(error); })
      .finally(function() { isLoading = false; });
    
  • Promise.prototype.constructor: 返回被創建的實例函數. 默認為 Promise 函數.

2.3 promise靜態方法

所謂靜態方法(static method)就是可以直接通過對象調用,而不用實例化的方法,即Promise.methodName();

  • Promise.resolve(value):返回一個狀態由給定value(可以是普通值,也可以是promise對象)決定的Promise對象。如果value是thenable(即,帶有then方法的對象),返回的Promise對象的最終狀態由then方法執行決定,否則返回成功的promise對象(狀態為fulfilled)

  • Promise.reject(reason):返回一個狀態為失敗(rejected)的Promise對象,并將給定的失敗信息傳遞給對應的處理方法

  • Promise.all(iterable): 一損俱損,只要一個失敗,就返回新的pomise對象,否則就等待所有的狀態提升為fulfilied。“團結”

    用于處理多個promise對象的狀態集合,集合中可以傳入常數,當做成功的promise返回值。當集合中一旦有一個promise轉改變為rejected,all的狀態就變為rejected:

    const p1 = Promise.resolve(1);
    const p2 = Promise.resolve(2);
    const p3 = Promise.reject(3);
    const p4 = Promise.resolve(4);
    // 沒有錯誤
    Promise.all([p1,p2,33, p4]).then(res => {
        console.log(res); // [1,2,33,4]
    })
    // 有錯誤
    Promise.all([p1,p2,p3, 33, p4]).then(res => {
        console.log(res); // 只要發現一個錯誤,就不會執行fulfilled方法
    }).catch(err => {
        console.log(err); // 執行錯誤,輸出3
    })
    

    當集合中所有的promise狀態都為fulfilled時,必須等待所有的promise執行完畢,即所有的promise對象狀態都提升為fulfilled再返回新dePromise對象,執行then方法。

  • Promise.race(iterable): 一榮俱榮,只要一個狀態改變(fulfilled或reject),就返回新的promise對象。“賽跑”

    用于處理多個promise對象的狀態集合,集合中可以傳入常數,當做成功的promise返回值。當集合中一旦有一個promise轉改變為fulfilled或rejected,all的狀態就提升:

    Promise.race([p1,p2,33,p4]).then(res=> {
        console.log(res); // 只會輸出其中一個狀態成功改變的,輸出:1
    });
    
    Promise.race([p1,p2,p3, 33,p4]).then(res=> {
        console.log(res); // 雖然p3出錯了,但是p1狀態先提升,只會執行狀態最先提升的,輸出:1
    }).catch(err => {
        console.log(err);// 這里不會執行
    });
    
  • *Promise.any(iterable): 處于提案中。。。只要參數實例有一個變成fulfilled狀態,包裝實例就會變成fulfilled狀態;如果所有參數實例都變成rejected狀態,包裝實例就會變成rejected狀態。

    手動實現如下:

    Promise.any = function(arr) {
        var self = this;
        return new Promise(function(resolve, reject){
            var errors = [];
            var count = 0;
            var len = arr.length;
            for(var i=0;i<len;i++){
                // 只要有一個實例狀態變為fulfilled,新的Promise狀態就會改變為fulfilled
                self.resolve(arr[i]).then(function(res){
                    resolve(res);
                }, function(err){
                    errors[count] = err;
                    count++;
                    // 否則等待所有的rejected,新的Promise狀態才會改變為rejected
                    if(count === len){
                        reject(errors)
                    }
                })
            }
        })
    }
    
  • *Promise.allSettled(iterable): ES2020將實現。。。 只有等到所有這些參數實例都返回結果,不管是fulfilled還是rejected,包裝實例才會結束。不關心結果,只關心有沒有執行完畢,該方法返回的新的 Promise 實例,一旦結束,狀態總是fulfilled,不會變成rejected

    手動實現如下:

    Promise.allSettled = function(arr){
        var results = [];
        var len = arr.length;
        for(var i=0;i<len;i++){
            this.resolve(arr[i]).then(function(res){
                results.push({status:'fulfilled', value: res});
            }, function(err){
                results.push({status:'rejected', value: err});
            })
        }
        // 一旦結束,狀態總是`fulfilled`,不會變成`rejected`
        return new Promise(function(resolve, reject) {
           resolve(results)
        })
    }
    
  • *Promise.try():事實上,Promise.try就是模擬try代碼塊,就像promise.catch模擬的是catch代碼塊。

    需求的提出:

    不論函數f是同步函數還是異步操作,但是想用 Promise 來處理它:

    Promise.resolve().then(f).catch()
    

    ? 但是,如果f是同步函數,那么它會在本輪事件循環的末尾執行。

    const f = () => console.log('now');
    Promise.resolve().then(f);
    console.log('next');
    // next
    // now
    

    有沒有方法,讓同步函數同步執行,異步函數異步執行,并且讓它們具有統一的 API捕獲錯誤:

    const f = () => console.log('now');
    (
      () => new Promise(
        resolve => resolve(f())
      )
    )();
    console.log('next');
    // now
    // next
    
    // 或者
    (async () => f())();
    console.log('next');
    // now
    // next
    

    Promise.try替代上面的代碼:

    const f = () => console.log('now');
    Promise.try(f);
    console.log('next');
    // now
    // next
    

    手動實現如下:

    Promise.try = function(fn){
        if(typeof fn !== 'function') return;
        return new Promise(function(resolve, reject) {
            return resolve(fn());
        })
    }
    

2.3 promise屬性

  • Promise.length:length屬性,其值總是1(構造器參數的數目)
  • Promise.prototypePromise構造器的原型

3. JS事件執行機制(Event Loop)

為了更好地理解promise的應用,先需要理解JS執行機制。

運行時概念

  • 函數調用形成了一個棧幀 (call stack)。

  • 對象被分配在一個堆(heap)中,即用以表示一大塊非結構化的內存區域。

  • 所有的異步操作都會進入隊列(queue)中等待被執行。

    [圖片上傳失敗...(image-11921a-1570631381694)]

一個 JavaScript 運行時包含了一個待處理的消息隊列。每一個消息都關聯著一個用以處理這個消息的函數。

函數的處理會一直進行到執行棧再次為空為止;然后事件循環將會處理隊列中的下一個消息(如果還有的話)。

事件循環機制

[圖片上傳失敗...(image-d11d0d-1570631381694)]

JS執行時,將任務分為同步任務異步任務,同步任務都在主線程上執行(主代碼塊),形成一個執行棧,異步任務會被加入到任務隊列里面。

任務隊列中的任務分為兩種任務類型:macrotask和microtask任務隊列里面微任務優先于宏任務執行,先執行完任務隊列里面所有的微任務,然后再執行任務隊列里面的宏任務。

  • 宏任務:script(主代碼塊), setTimeout, setInterval, setImmediate, I/O, UI rendering,MessageChannel、setImmediate(Node.js 環境)。每次執行棧執行的代碼就是一個宏任務

  • 微任務process.nextTick(nodejs相關), Promise, Object.observer, MutationObserver在當前宏任務(主線程) 執行結束后立即執行的任務

    [圖片上傳失敗...(image-6695ba-1570631381694)]

JS運行機制:

  • 執行棧中的宏任務(棧中沒有就從事件隊列中獲取),一般首先執行普通代碼塊(script)

  • 執行過程中如果遇到微任務,就將它添加到微任務隊列中

  • 當前宏任務執行完畢后,立即執行當前微任務隊列中的所有微任務(依次執行)

  • 當前宏任務執行完畢,開始檢查渲染,然后GUI線程接管渲染

  • 渲染完畢后,JS線程繼續接管,開始下一個宏任務(從事件隊列中獲取),以此循環...

    JS運行機制

參考:

4. promise測試題

4.1 示例一:

new Promise(resolve => {
// promise構造函數里面是同步代碼區,和普通代碼塊一樣
    console.log(5);
    resolve(1);
    
    Promise.resolve().then(() => {
        console.log(2)
    });
    console.log(4)
}).then(t => {
    console.log(t)
}).catch(()=>{
    console.log(6);
}).finally(() =>{
    console.log(0);
});
console.log(3);

<details>
<summary>輸出結果</summary>
<pre>5 4 3 2 1 0</pre>
</details>

4.2 示例二:

console.log('script start'); // 宏任務1

setTimeout(function() {
  console.log('setTimeout'); // 宏任務2
}, 0);

Promise.resolve().then(function() { 
  console.log('promise1');// 微任務1
}).then(function() {
  console.log('promise2'); // 微任務1
});

console.log('script end'); // 宏任務1

<details>
<summary>輸出結果</summary>
<pre>
script start
script end
promise1
promise2
setTimeout
</pre>
</details>

4.3 示例三:

let p1 = new Promise((resolve,reject)=>{
  let num = 6
  if(num<5){
    console.log('resolve1')
    resolve(num)
  }else{
    console.log('reject1')
    reject(num)
  }
})
p1.then((res)=>{
  console.log('resolve2')
  console.log(res)
},(rej)=>{
  console.log('reject2')
  let p2 = new Promise((resolve,reject)=>{
    if(rej*2>10){
      console.log('resolve3')
      resolve(rej*2)
    }else{
      console.log('reject3')
      reject(rej*2)
    }
  })
  return p2
}).then((res)=>{
  console.log('resolve4')
  console.log(res)
},(rej)=>{
  console.log('reject4')
  console.log(rej)
})

<details>
<summary>輸出結果</summary>
<pre>
reject1
reject2
resolve3
resolve4
12
</pre>
</details>

4.4 示例四:

Tasks, microtasks, queues and schedules

<iframe height="302" style="width: 100%;" scrolling="no" title="ES6-promise-demo3" src="https://codepen.io/keekuun/embed/abbzjbM?height=302&theme-id=0&default-tab=js,result" frameborder="no" allowtransparency="true" allowfullscreen="true">
See the Pen <a >ES6-promise-demo3</a> by Keekuun
(<a >@keekuun</a>) on <a >CodePen</a>.
</iframe>

<details>
<summary>點擊內部輸出</summary>
<pre>
click
promise
mutate
click
promise
mutate
timeout
timeout
</pre>
</details>

4.5 示例五:

從event loop到async await來了解事件循環機制

async function a1 () { // async關鍵字
    console.log('a1 start')
    await a2() // await關鍵字
    console.log('a1 end')
}
async function a2 () {
    console.log('a2')
}

console.log('script start')

setTimeout(() => {
    console.log('setTimeout')
}, 0)

Promise.resolve().then(() => {
    console.log('promise1')
})

a1()

let promise2 = new Promise((resolve) => {
    resolve('promise2.then')
    console.log('promise2')
})

promise2.then((res) => {
    console.log(res)
    Promise.resolve().then(() => {
        console.log('promise3')
    })
})
console.log('script end')

<details>
<summary>輸出結果</summary>
<pre>
script start
a1 start
a2
promise2
script end
promise1
a1 end
promise2.then
promise3
setTimeout
</pre>
</details>

4.6 示例五:

async function test() { 
    console.log('test start'); 
    await undefined; 
    console.log('await 1'); 
    await new Promise(resolve => {  
        console.log('promise in async'); 
        resolve(); 
    }); 
    console.log('await 2'); 
} 
 
test(); 
new Promise((resolve) => { 
    console.log('promise'); 
    resolve(); 
}) 
.then(() => {console.log(1)}) 
.then(() => {console.log(2)}) 
.then(() => {console.log(3)}) 
.then(() => {console.log(4)});

<details>
<summary>輸出結果</summary>
<pre>
test start
promise
await 1
promise in async
1
await 2
2
3
4
</pre>
</details>

5. 手寫promise

  • 使用ES5之前的語法實現:
(function () {
    // 判斷function
    function isFunction(fn) {
        return typeof fn === 'function';
    }

    // 狀態 pending、fulfilled、rejected
    var PENDING = 'pending';
    var FULFILLED = 'fulfilled';
    var REJECTED = 'rejected';

    // 構造方法
    var Kromise = function (handle) {
        // 當前狀態
        this._status = PENDING;
        // 添加成功回調隊列
        this._fulfilledQueue = [];
        // 添加失敗回調隊列
        this._rejectedQueue = [];
        // 引用當前this對象
        var self = this;

        if (!isFunction(handle)) {
            throw new Error('Parameter handle is not a function!')
        }

        // 添加resolve時執行的函數
        function _resolve(val) {
            var run = function () {
                if (self._status !== PENDING) return;
                // 依次執行成功隊列中的函數,并清空隊列
                var runFulfilled = function (res) {
                    var resolve;
                    while (resolve = self._fulfilledQueue.shift()) { // 出棧
                        resolve(res);
                    }
                };

                // 依次執行失敗隊列中的函數,并清空隊列
                var runRejected = function (err) {
                    var reject;
                    while (reject = self._rejectedQueue.shift()) { // 出棧
                        reject(err);
                    }
                };
                /* 如果resolve的參數為Kromise對象,則必須等待該Kromise對象狀態改變后,
                 * 當前Kromise的狀態才會改變,且狀態取決于參數Kromise對象的狀態
                 */
                if (val instanceof Kromise) {
                    val.then(function (value) {
                        self._status = FULFILLED;
                        self._value = value;
                        runFulfilled(value)
                    }, function (err) {
                        self._status = REJECTED;
                        self._value = err;
                        runRejected(err);
                    })
                } else {
                    self._status = FULFILLED;
                    self._value = val;
                    runFulfilled(val);
                }

            };
            // 為了支持同步的Promise,這里采用異步調用
            setTimeout(run, 0)
        }

        // 添加reject時執行的函數
        function _reject(err) {
            var run = function () {
                if (self._status !== PENDING) return;
                // 依次執行成功隊列中的函數,并清空隊列
                self._status = REJECTED;
                self._value = err;
                var reject;
                while (reject = self._fulfilledQueue.shift()) { // 出棧
                    reject(err);
                }
            };
            // 為了支持同步的Promise,這里采用異步調用
            setTimeout(run, 0)
        }

        // 執行handle,捕獲異常
        try {
            handle(_resolve.bind(this), _reject.bind(this));
        } catch (e) {
            _reject(e);
        }
    };

    // 屬性
    Kromise.length = 1;

    // 實例方法
    // 實現then方法
    Kromise.prototype.then = function (onFulfilled, onRejected) {
        var self = this;
        // 返回一個新的Kromise對象
        return new Kromise(function (onFulfilledNext, onRejectedNext) {
            // 成功時的回調
            var fulfilled = function (val) {
                try {
                    // 如果不是函數,值穿透
                    if (!isFunction(onFulfilled)) {
                        onFulfilledNext(val)
                    } else {
                        var res = onFulfilled(val);
                        // 如果當前回調函數返回Kromise對象,必須等待其狀態改變后在執行下一個回調
                        if (res instanceof Kromise) {
                            res.then(onFulfilledNext, onRejectedNext);
                        } else {
                            //否則會將返回結果直接作為參數,傳入下一個then的回調函數,并立即執行下一個then的回調函數
                            onFulfilledNext(res);
                        }
                    }
                } catch (e) {
                    // 如果函數執行出錯,新的Kromise對象的狀態為失敗
                    onRejectedNext(e);
                }
            };
            // 失敗時的回調
            var rejected = function (err) {
                try {
                    if (!isFunction(onRejected)) {
                        onRejectedNext(err)
                    } else {
                        var res = onRejected(err);
                        if (res instanceof Kromise) {
                            res.then(onFulfilledNext, onRejectedNext);
                        } else {
                            onFulfilledNext(res);
                        }
                    }
                } catch (e) {
                    onRejectedNext(e)
                }
            };

            switch (self._status) {
                // 當狀態為pending時,將then方法回調函數加入執行隊列等待執行
                case PENDING:
                    self._fulfilledQueue.push(fulfilled);
                    self._rejectedQueue.push(rejected);
                    break;
                // 當狀態已經改變時,立即執行對應的回調函數
                case FULFILLED:
                    fulfilled(self._value);
                    break;
                case REJECTED:
                    rejected(self._value);
                    break;
            }
        });
    };

    // 實現catch方法
    Kromise.prototype.catch = function (onRejected) {
        return this.then(undefined, onRejected);
    };

    // 實現finally方法
    Kromise.prototype.finally = function (onFinally) {
        return this.then(function (value) {
            Kromise.resolve(onFinally()).then(function () {
                return value;
            })
        }, function (err) {
            Kromise.resolve(onFinally()).then(function () {
                throw new Error(err);
            })
        })
    };

    // 靜態方法
    // 實現resolve方法
    Kromise.resolve = function (value) {
        // 如果參數是Kromise實例,直接返回這個實例
        if (value instanceof Kromise) {
            return value;
        }
        return new Kromise(function (resolve) {
            resolve(value)
        })
    };
    // 實現reject方法
    Kromise.reject = function (value) {
        return new Kromise(function (resolve, reject) {
            reject(value)
        })
    };
    // 實現all方法
    Kromise.all = function (arr) {
        var self = this;
        return new Kromise(function (resolve, reject) {
            var values = [];
            for (var i = 0, len = arr.length; i < len; i++) {
                // 數組參數如果不是Kromise實例,先調用Kromise.resolve
                self.resolve(arr[i]).then(function (res) {
                    values.push(res);
                    // 所有狀態都變成fulfilled時返回的Kromise狀態就變成fulfilled
                    if (values.length === arr.length) {
                        resolve(values);
                    }
                }, function (e) {
                    // 有一個被rejected時返回的Kromise狀態就變成rejected
                    reject(e);
                })
            }
        })
    };

    // 實現race方法
    Kromise.race = function (arr) {
        var self = this;
        return new Kromise(function (resolve, reject) {
            for (var i = 0, len = arr.length; i < len; i++) {
                // 只要有一個實例率先改變狀態,新的Kromise的狀態就跟著改變
                self.resolve(arr[i]).then(function (res) {
                    resolve(res);
                }, function (err) {
                    reject(err);
                })
            }
        })
    };
    // 實現any方法
    Kromise.any = function (arr) {
        var self = this;
        return new Kromise(function (resolve, reject) {
            var count = 0;
            var errors = [];
            for (var i = 0, len = arr.length; i < len; i++) {
                // 只要有一個實例狀態變為fulfilled,新的Kromise狀態就會改變為fulfilled
                self.resolve(arr[i]).then(function (res) {
                    resolve(res);
                }, function (err) {
                    errors[count] = err;
                    count++;
                    // 否則等待所有的rejected,新的Kromise狀態才會改變為rejected
                    if (count === arr.length) {
                        reject(errors);
                    }
                })
            }
        })

    };
    // 實現allSettled方法
    Kromise.allSettled = function (arr) {
        var results = [];
        var len = arr.length;
        for (var i = 0; i < len; i++) {
            this.resolve(arr[i]).then(function (res) {
                results.push({status: FULFILLED, value: res});
            }, function (err) {
                results.push({status: REJECTED, value: err});
            })
        }
        // 一旦結束,狀態總是`fulfilled`,不會變成`rejected`
        return new Kromise(function (resolve, reject) {
            resolve(results)
        })
    };
    // 實現try方法
    Kromise.try = function (fn) {
        if (!isFunction(fn)) return;
        return new Kromise(function (resolve, reject) {
            return resolve(fn());
        })
    };

    // 掛載
    window.Kromise = Kromise;
})();

  • 使用ES6 class語法實現
  // 判斷變量否為function
  const isFunction = variable => typeof variable === 'function'
  // 定義Promise的三種狀態常量
  const PENDING = 'PENDING'
  const FULFILLED = 'FULFILLED'
  const REJECTED = 'REJECTED'

  class MyPromise {
    constructor (handle) {
      if (!isFunction(handle)) {
        throw new Error('MyPromise must accept a function as a parameter')
      }
      // 添加狀態
      this._status = PENDING
      // 添加狀態
      this._value = undefined
      // 添加成功回調函數隊列
      this._fulfilledQueues = []
      // 添加失敗回調函數隊列
      this._rejectedQueues = []
      // 執行handle
      try {
        handle(this._resolve.bind(this), this._reject.bind(this)) 
      } catch (err) {
        this._reject(err)
      }
    }
    // 添加resovle時執行的函數
    _resolve (val) {
      const run = () => {
        if (this._status !== PENDING) return
        // 依次執行成功隊列中的函數,并清空隊列
        const runFulfilled = (value) => {
          let cb;
          while (cb = this._fulfilledQueues.shift()) {
            cb(value)
          }
        }
        // 依次執行失敗隊列中的函數,并清空隊列
        const runRejected = (error) => {
          let cb;
          while (cb = this._rejectedQueues.shift()) {
            cb(error)
          }
        }
        /* 如果resolve的參數為Promise對象,則必須等待該Promise對象狀態改變后,
          當前Promsie的狀態才會改變,且狀態取決于參數Promsie對象的狀態
        */
        if (val instanceof MyPromise) {
          val.then(value => {
            this._value = value
            this._status = FULFILLED
            runFulfilled(value)
          }, err => {
            this._value = err
            this._status = REJECTED
            runRejected(err)
          })
        } else {
          this._value = val
          this._status = FULFILLED
          runFulfilled(val)
        }
      }
      // 為了支持同步的Promise,這里采用異步調用
      setTimeout(run, 0)
    }
    // 添加reject時執行的函數
    _reject (err) { 
      if (this._status !== PENDING) return
      // 依次執行失敗隊列中的函數,并清空隊列
      const run = () => {
        this._status = REJECTED
        this._value = err
        let cb;
        while (cb = this._rejectedQueues.shift()) {
          cb(err)
        }
      }
      // 為了支持同步的Promise,這里采用異步調用
      setTimeout(run, 0)
    }
    // 添加then方法
    then (onFulfilled, onRejected) {
      const { _value, _status } = this
      // 返回一個新的Promise對象
      return new MyPromise((onFulfilledNext, onRejectedNext) => {
        // 封裝一個成功時執行的函數
        let fulfilled = value => {
          try {
            if (!isFunction(onFulfilled)) {
              onFulfilledNext(value)
            } else {
              let res =  onFulfilled(value);
              if (res instanceof MyPromise) {
                // 如果當前回調函數返回MyPromise對象,必須等待其狀態改變后在執行下一個回調
                res.then(onFulfilledNext, onRejectedNext)
              } else {
                //否則會將返回結果直接作為參數,傳入下一個then的回調函數,并立即執行下一個then的回調函數
                onFulfilledNext(res)
              }
            }
          } catch (err) {
            // 如果函數執行出錯,新的Promise對象的狀態為失敗
            onRejectedNext(err)
          }
        }
        // 封裝一個失敗時執行的函數
        let rejected = error => {
          try {
            if (!isFunction(onRejected)) {
              onRejectedNext(error)
            } else {
                let res = onRejected(error);
                if (res instanceof MyPromise) {
                  // 如果當前回調函數返回MyPromise對象,必須等待其狀態改變后在執行下一個回調
                  res.then(onFulfilledNext, onRejectedNext)
                } else {
                  //否則會將返回結果直接作為參數,傳入下一個then的回調函數,并立即執行下一個then的回調函數
                  onFulfilledNext(res)
                }
            }
          } catch (err) {
            // 如果函數執行出錯,新的Promise對象的狀態為失敗
            onRejectedNext(err)
          }
        }
        switch (_status) {
          // 當狀態為pending時,將then方法回調函數加入執行隊列等待執行
          case PENDING:
            this._fulfilledQueues.push(fulfilled)
            this._rejectedQueues.push(rejected)
            break
          // 當狀態已經改變時,立即執行對應的回調函數
          case FULFILLED:
            fulfilled(_value)
            break
          case REJECTED:
            rejected(_value)
            break
        }
      })
    }
    // 添加catch方法
    catch (onRejected) {
      return this.then(undefined, onRejected)
    }
    // 添加靜態resolve方法
    static resolve (value) {
      // 如果參數是MyPromise實例,直接返回這個實例
      if (value instanceof MyPromise) return value
      return new MyPromise(resolve => resolve(value))
    }
    // 添加靜態reject方法
    static reject (value) {
      return new MyPromise((resolve ,reject) => reject(value))
    }
    // 添加靜態all方法
    static all (list) {
      return new MyPromise((resolve, reject) => {
        /**
         * 返回值的集合
         */
        let values = []
        let count = 0
        for (let [i, p] of list.entries()) {
          // 數組參數如果不是MyPromise實例,先調用MyPromise.resolve
          this.resolve(p).then(res => {
            values[i] = res
            count++
            // 所有狀態都變成fulfilled時返回的MyPromise狀態就變成fulfilled
            if (count === list.length) resolve(values)
          }, err => {
            // 有一個被rejected時返回的MyPromise狀態就變成rejected
            reject(err)
          })
        }
      })
    }
    // 添加靜態race方法
    static race (list) {
      return new MyPromise((resolve, reject) => {
        for (let p of list) {
          // 只要有一個實例率先改變狀態,新的MyPromise的狀態就跟著改變
          this.resolve(p).then(res => {
            resolve(res)
          }, err => {
            reject(err)
          })
        }
      })
    }
    finally (cb) {
      return this.then(
        value  => MyPromise.resolve(cb()).then(() => value),
        reason => MyPromise.resolve(cb()).then(() => { throw reason })
      );
    }
  }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370