裏外煎透promise,配菜co模塊&async

一、promise

(一)、promise簡介:

爲解決回調地獄而誕生,比傳統的回調函數和事件都要更強大,是一種異步編程的解決方案;其主要是將 異步操作 以 同步操作的流程 表達,避免層層嵌套。
每一個promise都保存着未來結束的事件,如異步操作結果。提供統一api,處理各種異步操作。
另外,promise立即執行,所以創建時,裏面的語句會馬上執行,有打印會出現,也常出現在面試題中;無法取消,如果沒有結束狀態的標誌resolve和reject則不會反饋到外部狀態。
Promise構造函數,生成Promise實例,接收兩個參數resolve和reject函數作爲參數,分別用於將pending轉爲fulfilled或者rejected狀態,傳遞出去結果。

(二)promise的方法:

then:兩個函數參數,最後一個可省略,鏈式調用;
catch用於捕捉catch到的錯誤,catch中的錯誤在後面沒有鏈式的時候不會傳遞出去
finally一個回調函數,無參數,最終都會操作

(以上都在原型對象上)

resolve()、rejected()將對象轉爲promise,參數:空、thenable、原始值、普通對象,都會轉爲promise;立即執行thenable
all將多個promise實例包裝一個,具有iterator接口,且返回的每個成員都是promise實例,所有都是fulfilled才resolved
race跟着率先改變狀態而改變,無論fulfilled還是rejected
allSettled只有等到所有這些參數實例都返回結果,不管是fulfilled還是rejected,包裝實例纔會結束。該方法由 ES2020 引入。
any只要一個變成fulfilled,整體就會變成fulfilled,還是提案
try用於無論要執行的是異步同步都想用promise來執行,這樣就可以用統一的api來then下一步catch捕捉錯誤;try的替代方案有如下:
const f = () => console.log('now');
(
  () => new Promise(
    resolve => resolve(f())
  )
)();
console.log('next');

(async () => f())().then(...).catch(...)


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

(三)手寫實現promise(class類繼承實現)

/*
1、base:resolve\reject\then

2、異步操作
存儲兩種回調函數;
調用then時,如果狀態還未改變,就向兩數組中增加then參數對應的方法;如果已噶變,直接使用當前結果傳入then方法執行;
resolve、reject執行,觸發對應數組中的函數;

3、鏈式操作
resolve、reject可傳入promise實例作爲參數;
在then中返回一個新的promise,如果狀態未改變,在該promise的構造函數中執行存儲回調函數;否則立即執行回調函數
catch、finally都是then函數的調用

4、all、race、allSettled、any
all 全部轉爲fulfilled才轉變fulfilled
race 先返回第一個轉變狀態的
allSettled 全部都結束,轉爲fulfilled,返回一個數組  ES2020
any 任意一個轉爲fulfilled,整體就是fulfilled;如果都是rejected,整體才爲rejected; vs race  提案

借鑑網址:http://www.360doc.com/content/19/0705/17/13328254_846904587.shtml#
Promise/A+:https://promisesaplus.com/
*/

const pending = 1;
const fulfilled = 2;
const rejected = 3;
class MyPromise{
    constructor(executor){
        const _this = this
        _this.status = pending;
        _this.val = null;
        _this.val = null;
        _this.resolveCallback = [];
        _this.rejectedCallback = [];

        function resolve(val){
            // 先判斷val是否爲promise,若是,調用then
            if(val instanceof MyPromise){
                val.then(resolve, reject)
            }
            // 判斷當前狀態
            if(_this.status === pending){
                _this.status = fulfilled;
                _this.val = val;
                _this.resolveCallback.forEach(fn => fn(val)); // 異步執行
            }
        }

        function reject(err){
            // 先判斷err是否爲promise,若是,調用then
            if(err instanceof MyPromise){
                err.then(resolve, reject)
            }
            if(_this.status === pending){
                _this.status = rejected;
                _this.err = err;
                _this.rejectedCallback.forEach(fn => fn(err));
            }
        }

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

    then(onResolve, onReject){
        onResolve = typeof onResolve === 'function' ? onResolve: val => val
        onReject = typeof onReject === 'function' ? onReject: err => { throw err }
        // then要轉爲promise對象。以防具備promise
        return new MyPromise((resolve, reject) => {
            if(this.status === pending){ // 存儲回調
                this.resolveCallback.push(() => {
                    try{
                        resolvePromise(onResolve(this.val), resolve, reject)
                    }catch(err){
                        reject(err)
                    }
                });
                this.rejectedCallback.push(() => {
                    try{
                        resolvePromise(onReject(this.err), resolve, reject)
                    }catch(err){
                        reject(err)
                    }
                });
            }else if(this.status === fulfilled){
                setTimeout(() => {
                    try{
                        resolvePromise(onResolve(this.val), resolve, reject)
                    }catch(e){
                        reject(e)
                    }
                });
            }else if(this.status === rejected){
                setTimeout(() => {
                    try{
                        onReject(this.err)
                    }catch(e){
                        reject(e)
                    }
                });
            }
        })
    }

    catch(onReject){
        return this.then(null, onReject)
    }

    finally(callback){
        return this.then(callback, callback)
    }

    static resolve(val){
        return val instanceof MyPromise ? val: new MyPromise(resolve => {
            return resolve(val)
        })
    }

    static reject(err){
        return err instanceof MyPromise ? err: new MyPromise(null, reject => {
            return reject(err)
        })
    }

    static all(promises){
        // 先檢測數組類型、轉數組
        promises = Array.isArray(promises) ? promises: [promises];
        // 返回新的promise類型
        return new MyPromise((resolve, reject) => {
            const len = promises.length;
            let values = []; // 結果組成的數組
            let counter = 0;
            promises.forEach((eachPromise, index) => {
                if(!(eachPromise instanceof MyPromise)){
                    // 不是promise實例調用全部resolve
                    eachPromise = MyPromise.resolve(eachPromise)
                }
                eachPromise.then(ret => {
                    values[index] = ret;
                    if(++counter === len){
                        resolve(values)
                    }
                }, reject)
            })
        })
    }

    static race(promises){
        // 先檢測數組類型、轉數組
        promises = Array.isArray(promises) ? promises: [promises];
        // 返回新的promise類型
        return new MyPromise((resolve, reject) => {
            promises.forEach((eachPromise, index) => {
                if(!(eachPromise instanceof MyPromise)){
                    // 不是promise實例調用全部resolve
                    eachPromise = MyPromise.resolve(eachPromise)
                }
                eachPromise.then(resolve, reject)
            })
        })
    }

    static allSettled(promises){
        // 先檢測數組類型、轉數組
        promises = Array.isArray(promises) ? promises: [promises];
        const len = promises.length;
        let values = [], counter = 0;
        // 返回新的promise類型
        return new MyPromise((resolve, reject) => {
            promises.forEach((eachPromise, index) => {
                if(!(eachPromise instanceof MyPromise)){
                    // 不是promise實例調用全部resolve
                    eachPromise = MyPromise.resolve(eachPromise)
                }
                eachPromise.then(ret => {
                    values[index] = ret;
                    if(++counter === len){
                        resolve(values)
                    }
                }, err => {
                    values[index] = err;
                    if(++counter === len){
                        resolve(values)
                    }
                })
            })
        })
    }

    static any(promises){
        // 先檢測數組類型、轉數組
        promises = Array.isArray(promises) ? promises: [promises];
        const len = promises.length;
        let values = [], fulfilledCounter = 0, rejectedCounter = 0;
        // 返回新的promise類型
        return new MyPromise((resolve, reject) => {
            promises.forEach((eachPromise, index) => {
                if(!(eachPromise instanceof MyPromise)){
                    // 不是promise實例調用全部resolve
                    eachPromise = MyPromise.resolve(eachPromise)
                }
                eachPromise.then(ret => {
                    values[index] = ret;
                    fulfilledCounter += 1;
                    if(fulfilledCounter + rejectedCounter === len){
                        resolve(values)
                    }
                }, err => {
                    values[index] = err;
                    rejectedCounter += 1;
                    if(fulfilledCounter + rejectedCounter === len){
                        if(rejectedCounter === len){
                            reject(values)
                        }
                    }
                })
            })
        })
    }
}


function resolvePromise(retValue, resolve, reject){
    // 1、判斷是否爲promise,不是resolve出結果
    if(retValue instanceof MyPromise){
        // 2、判斷當前狀態是否結束,未結束繼續then, 結束調用then
        if(retValue.status === pending){
            retValue.then((ret) => {
                resolvePromise(ret, resolve, reject)
            }, err => {
                reject(err)
            })
        }else{
            retValue.then(resolve, reject)
        }
    }else{
        resolve(retValue)
    }
}


// 測試用例
var p1 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve(1)
    }, 2000)
});
var p2 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve(2)
    }, 1000)
});
var p3 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        reject("p3 error")
    }, 1000)
});
var p4 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve("p4")
    }, 1000)
});
MyPromise.all([p1, p2,'hhhh', p4]).then(res => {
    console.log("all res ---> ", res);
}, err => {
    console.log("all1 err ---> ", err)
});
MyPromise.all([p1, p2, p3, p4, 'hhhh']).then(res => {
    console.log("all res ---> ", res);
}, err => {
    console.log("all2 err ---> ", err)
});
MyPromise.race([p1, p2, p3, p4, 'hhhh']).then(res => {
    console.log("race res ---> ", res);
});
MyPromise.allSettled([p1, p2, p3, p4, 'hhhh']).then(res => {
    console.log("allSettled res ---> ", res);
});
MyPromise.any([p3, p4, 'hhhh']).then(res => {
    console.log("any res ---> ", res);
});

// 打印如下
/*
race res --->  hhhh
all2 err --->  p3 error
any res --->  [ 'p3 error', 'p4', 'hhhh' ]
all res --->  [ 1, 2, 'hhhh', 'p4' ]
allSettled res --->  [ 1, 2, 'p3 error', 'p4', 'hhhh' ]
*/

npm有很多不錯的包,async和co模塊就是爲了很好的管理同步異步都操作的情況

二、co模塊簡介

co模塊一個異步操作容器。用於Generator函數的自動執行,不用一步步next,不用寫yield。
co的自動執行需要一種機制,當異步操作有了結果,能夠交回執行權。
使用兩種方式可以做到:
(1)回調函數;將異步操作包裝成Thunk函數,在回調函數裏面交回執行權
(2)Promise對象;將異步操作包裝成Thunk函數,在then裏面交回執行權
核心代碼如下:
function co(gen) {
    var ctx = this;
    return new Promise(function (resolve, reject) {
        if (typeof gen === 'function') gen = gen.call(ctx);
        if (!gen || typeof gen.next !== 'function') return resolve(gen);
        onFulfilled();
        function onFulfilled(res) {
            var ret;
            try {
                ret = gen.next(res);
            } catch (e) {
                return reject(e);
            }
            next(ret);
        }
    });
    function next(ret) {
        if (ret.done) return resolve(ret.value);
        var value = toPromise.call(ctx, ret.value);
        if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
        return onRejected(
            new TypeError(
                'You may only yield a function, promise, generator, array, or object, '
                + 'but the following object was passed: "'
                + String(ret.value)
                + '"'
            )
        );
    }
}

三、async模塊

參考:阮一峯ES6 入門教程

1、簡介

這麼巧,async也是對generator函數的改進:
1、內置執行器
執行與普通函數的寫法一致,每一步一行;co則靠的定義好之後,不停的next
2、更好的語義
async表示函數裏有異步操作,await表示緊跟在後面的表達式需要等待結果。
3、更廣的適用性
co模塊約定,yield命令後面只能是 Thunk 函數或 Promise 對象,而async函數的await命令後面,可以是 Promise 對象和原始類型的值(數值、字符串和布爾值,但這時會自動轉成立即 resolved 的 Promise 對象)。
4、返回值是 Promise。
async函數的返回值是 Promise 對象,這比 Generator 函數的返回值是 Iterator 對象方便多了。你可以用then方法指定下一步的操作。
進一步說,async函數完全可以看作多個異步操作,包裝成的一個 Promise 對象,而await命令就是內部then命令的語法糖。

2、使用

async函數返回一個 Promise 對象,可以使用then方法添加回調函數。當函數執行的時候,一旦遇到await就會先返回,等到異步操作完成,再接着執行函數體內後面的語句。

function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}

asyncPrint('hello world', 50);
// 控制檯:
Promise {<pending>}
    __proto__: Promise
    [[PromiseStatus]]: "resolved"
    [[PromiseValue]]: undefined
hello world

3、語法

據說,難點是錯誤處理機制。
1、async函數裏面可以有return返回語句,返回信息會自動作爲thenable的參數,永遠堅持返回一個promise對象;
因此內部拋出錯誤,就會是promise變爲reject,然後被捕獲。在沒有遇到錯誤的時候,一定會將await後面的執行完,再then;如果await後面的異步操作出錯,那麼等同於async函數返回的 Promise 對象被reject。所以最好把await命令放在try…catch代碼塊中。
2、正常情況下,await命令後面是一個 Promise 對象,返回該對象的結果。如果不是 Promise 對象,就直接返回對應的值。是一個thenable對象(即定義了then方法的對象),那麼await會將其等同於 Promise 對象。

使用技巧:

1、最好把await命令放在try…catch代碼塊中,以便捕獲rejected
2、多個await命令後面的異步操作,如果不存在繼發關係,最好讓它們同時觸發。promise.all
3、await命令只能用在async函數之中,如果用在普通函數,就會報錯。比如forEach --> 替代for或者reduce(後者還不太瞭解,待研究)
4、async 函數可以保留運行堆棧。

const a = () => {
  b().then(() => c());
};

上面代碼中,函數a內部運行了一個異步任務b()。當b()運行的時候,函數a()不會中斷,而是繼續執行。等到b()運行結束,可能a()早就運行結束了,b()所在的上下文環境已經消失了。如果b()或c()報錯,錯誤堆棧將不包括a()。
現在將這個例子改成async函數。

const a = async () => {
  await b();
  c();
};

上面代碼中,b()運行的時候,a()是暫停執行,上下文環境都保存着。一旦b()或c()報錯,錯誤堆棧將包括a()。

4、實現原理

function spawn(genF) {
  return new Promise(function(resolve, reject) {
    const gen = genF();
    function step(nextF) {
      let next;
      try {
        next = nextF();
      } catch(e) {
        return reject(e);
      }
      if(next.done) {
        return resolve(next.value);
      }
      Promise.resolve(next.value).then(function(v) {
        step(function() { return gen.next(v); });
      }, function(e) {
        step(function() { return gen.throw(e); });
      });
    }
    step(function() { return gen.next(undefined); });
  });
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章