從手寫Promise到async/await

導言:在學習 async/awit以前, 咱們頗有必要學習一下 , 迭代器(lterator)生成器(Generator)html

迭代器(lterator)和生成器(Generator)

導言:用循環語句迭代數據時,必需要初始化有關變量來記錄每一次迭代在數據集合中的位置,
迭代器的使用能夠極大地簡化數據操做,因而es6也向js中添加了這個迭代器特性。新的數組方法和新的集合類型(例如:Set與Map集合)都依賴迭代器的實現,甚至異步編程中均可以使用迭代器。

但這此以前,咱們必定要了解一下迭代器的背後的歷史前端

循環語句的問題

var colors = ['red','green','blue'];
for(var i = 0 ,len = colors.length ; i < len ; i ++) {
    console.log(colors[i])
}

上面是一段標準的for循環代碼,經過變量i來跟蹤colors數組的索引。

雖然循環語句的語法簡單,可是若是將多個循環嵌套則須要追蹤多個變量,代碼的複雜度會大大增長。迭代器的出現爲了消除這種複雜性並減小循環中的錯誤。java

什麼是迭代器

迭代器是一種特殊對象,它具備一些專門爲迭代過程設計的專有接口,全部的迭代器對象都有一個next()方法,每次調用都返回一個結果對象。結果對象有兩個屬性:一個是value,表示下一個將要返回的值;另外一個是done, 它是一個布爾類型的值,當沒有更多可返回數據時返回數據時返回true。迭代器還會保存一個內部指針,用來指向當前集合中值的位置,每調用一次next()方法,都會返回下一個可用的值。

咱們用es5的語法來本身寫一個迭代器

按個人理解,這應該屬於閉包的用處node

function createIterator(items) {
var i = 0;
    return {
        next :function() {
            var done = ( i >= items.length);
            var value = !done ? items[i++] :undefined;
            return {
                done : done,
                value : value
            }
        }
    }
}

var iterator = createIterator([1,2,3]);
console.log(iterator.next()) //{ done: false, value: 1 }
console.log(iterator.next()) //{ done: false, value: 2 }
console.log(iterator.next()) //{ done: false, value: 3 }
console.log(iterator.next()) //{ done: true, value: undefined }
console.log(iterator.next()) //{ done: true, value: undefined }

迭代器的編寫規則也一樣複雜,但es6同時還引入一個生成器對象,它可以讓建立迭代器對象過程變得簡單。

note: 檢測對象是否爲可迭代對象es6

function isIterable(object) {
        return typeof object[Symbol.iterator] === "function";
    }

什麼是生成器

生成器是一種返回迭代器的函數,經過 function 關鍵字後的 星號(*)來表示, 函數中會用到新的關鍵字 yield 。就像這樣 :面試

//生成器
function *createIterator() {
    yield 1;
    yield 2;
    yield 3;
}
// 生成器的調用方式與普通函數相同, 只不過返回的是一個迭代器
let iterator = createIterator();
console.log(iterator.next())//{ value: 1, done: false }
console.log(iterator.next())//{ value: 2, done: false }       
console.log(iterator.next())//{ value: 3, done: false }
console.log(iterator.next())//{ value: undefined, done: true }

在這個實例中, createIterator()前的星號代表它是一個生成器;yield 關鍵字也是 es6的新特性 , 可用經過它來指定調用迭代器的 next()方法時的返回值及返回順序。生成迭代器後, 連續3次調用它的next()方法返回3個不一樣的值 , 分別是 1,2,3。 生成器的調用過程與其餘函數同樣 , 最終返回的是建立好的迭代器。

生成器函數最重要的是,每當執行完一條 yield 語句後函數就會自動中止執行.ajax

異步任務執行

生成器使人興奮的特性多與異步編程有關, javaScript中的異步編程有利有弊:簡單任務的異步化很是容易實現;而複雜任務的異步化會帶來不少管理代碼的挑戰。因爲生成器支持在函數中暫停代碼執行,於是能夠深刻挖掘異步處理的更多用法。若是須要嵌套回調化序列化一系列的異步操做,事情會變得很是複雜。此時,生成器和yield語句就派上用場了。編程

簡單任務執行器

因爲執行yield 語句會暫停當前函數的執行過程並等待下一次調用 next()方法。所以你能夠建立一個函數,在函數中調用生成器生成相應的迭代器,從而在不用回調函數的基礎上實現異步調用next()方法,就像這樣:數組

function run(taskDef) {
    // 建立一個無使用限制的迭代器
    let task = taskDef();
    // 開始執行任務
    let result = task.next();
    //循環調用next()的函數
    function step() {
        // 若是任務未完成,則繼續執行
        if(!result.done) {
            result = task.next();
            step();
        }
    }
    //開始迭代執行
    step();
}
// 執行
run(function *() {
    console.log(1);
    yield;
    console.log(2);
    yield;
    console.log(3)
})

函數run()接收一個生成器函數做爲參數,這個函數定義了後續要執行的任務,生成一個迭代器並將它存儲其在變量task中。首次調用迭代器的next()方法時,返回的結果被存儲起來稍後繼續使用。step()函數會檢查result.done的值,若是爲false則執行迭代器的next()方法,並再次執行step()操做。每次調用next()方法,返回的最新信息老是覆蓋變量result。在代碼的最後,初始化執行step()函數並開始整個的迭代過程,每次經過檢查result.done來肯定是否有更多任務須要執行。promise

向任務執行器傳遞數據

給任務執行器傳遞數據的最簡單方法是,將值經過迭代器的next()方法傳入做爲yield的生成值供下次調用。這下面這段代碼中,只須要將result.value傳入next()方法便可:

function run(taskDef) {
    // 建立一個無使用限制的迭代器
    let task = taskDef();
    //開始執行任務
    let result = task.next();
    // 循環調用next()的函數
    function step() {
        //若是任務未完成 , 則繼續執行
        if(!result.done) {
            result = task.next(result.value);
            step();
        }
    }
    //開始迭代執行
    step();
}

run(function *() {
    let value = yield 1;
    console.log(value); // 1

    value = yield value + 3;
    console.log(value);
})

這例子會向控制檯輸出兩個數值 1 和 4.

如今數據已經可以在yield調用間互相傳遞了,只須要一個小小的改變便能支持異步調用。

異步任務執行器

以前的例子只是在多個yield調用間來回傳遞靜態數據,而等待一個異步過程有些不一樣。任務執行器須要知曉回調函數是什麼以及如何使用它。因爲 yield表達式會將值返回給任務執行器,全部的函數調用都會返回一個值,所以在某種程度上這也是一個異步操做,任務執行器會一直等待直到操做完成。

下面咱們定義一個異步操做:
        
        function fetchData() {
            return function(callback) {
                setTimeout(function() {
                    callback(null,'Hi');
                }, 50)
            }
        }

理解了函數中異步過程的運做方式,咱們能夠將任務執行器稍做修改。當result.value是一個函數時,任務執行器會先執行這個函數再講結果傳入next()方法,代碼更新以下 :

function run(taskDef) {
    // 建立一個無使用限制的迭代器
    let task = taskDef();
    //開始執行任務
    let result = task.next();
    // 循環調用next()函數
    function step() {
        //若是任務未完成 , 則繼續執行
        if(!result.done) {
            if(typeof result.value === "function") {
                result.value(function(err,data) {
                    if(err) {
                        result = task.throw(err);
                        return;
                    }
                    result = task.next(data);
                    step();
                });
            } else {
                result = task.next(result.value);
                step();
            }
        }
    }
    //開始執行迭代
    step();
}

經過 === 操做符檢查後 , 若是 result.value 是一個函數,會傳入一個回調函數做爲參數來調用它,回調函數遵循node.js中有關執行錯誤的約定;所可能的錯誤放在第一個參數(err)中,結果放在第二個參數中。若是傳入了err,則意味着執行過程當中產生了cuow,這時會經過task.throw()正確輸出錯誤對象。若是 result.value 不是一個函數,則直接將其傳入next()方法。

如今,這個新版的任務執行器已經能夠用於全部的異步任務了。在node.js環境中,若是要從文件中讀取一些數據,須要在fs.readFile()外圍建立一個包裝器(wrapper), 返回一個與 fetchData()相似的函數

let fs = require('fs');
    function readFile(filename) {
        return function(callback) {
            fs.readFile(filename,'utf8',callback);
        }
    }
    //調用
    run(function *(){
        let contents = yield readFile('1.txt');
        console.log(contents);
    })

Promise與異步編程

哎,咱們終於開始學習Promise了,接一來,咱們將學

  • Promise出現的緣由是什麼
  • Promise是什麼
  • Promise的基本概念
  • 最後咱們將親自手寫一個簡單的Promise

異步編程背景知識

javaScript引擎是基於單線程和事件循環的概念構建的,同一時刻只容許一個代碼塊執行。

Promise出現的緣由

那麼,若是咱們有一天必須控制 兩段異步操做的執行順序時,咱們該怎麼辦呢?

好比,咱們必須先 讀1.txt文件 , 再複製一份 2.txt文件。
咱們知道 , I/O操做都是異步的 , 控制順序的話,咱們能夠這樣:

fs.readFile('./1.txt','utf8',(err,data)=>{
    fs.writeFile('./2.txt',data,(err)=>{
        if(err) {
            return;
        }
    })
})

簡單來講,就是把寫的操做放在讀的裏面來執行。但這樣不太好,由於一旦要操做異步的順序的代碼多了起來,一旦不知道哪裏出了 bug ,就會很難調試,看的頭都會大

Promise是什麼

Promise是一種異步操做的解決方案,將寫法複雜的傳統的回調函數和監聽事件的異步操做,用同步代碼的形式表達出來。避免了多級異步操做的回調函數嵌套。

Promise的基本概念

Promise至關於異步操做結果的佔位符,它不會訂閱一個事件,也不會傳遞一個回調函數給目標函數,而是讓函數返回一個Promise

Promise的生命週期

每一個Promise都會經歷一個短暫的生命週期 :先是處於進行中(pending)的狀態,此時操做還沒有完成,因此它也是未處理(unsettled)的;一但異步操做執行結束,Promise在變成下面其中一個:

  • Fulfilled Promise異步操做成功完成
  • Rejected 因爲程序錯誤或一些其餘緣由,Promise異步操做未能成功完成。

note:若是一個對象實現了then()方法,那這個對象咱們稱之爲 thenable 對象。全部的Promise都是thenable對象,但並不是全部thenable對象都是Promise.

有了Promise,咱們即可以將上面的讀寫操做,變成下面這樣了:

//讀
function readPath(path) {
    return new Promise((resolve,reject)=>{
        fs.readFile(path,'utf8',(error,data)=>{
            if(error) {
                reject(error);
            } else {
                resolve(data)
            }
        })
    })
}
//寫
function wirtePath(path,data) {
    return new Promise((resolve,reject)=>{
        fs.writeFile(path,data,(error)=>{
            if(error) {
                reject(error)
            } else {
                resolve('success')
            }
        })
    })
}

readPath('./1.txt')
    .then((data)=>{
        wirtePath('./2.txt',data);
    },(reason)=>{
        console.log(reason);
    })

更好的異步任務執行: 其基本思想爲

用 async 標記的函數 代替生成器 , 用 await 代替 yield 來調用函數,就像這樣

async function (){
    let content = await readPath('./1.txt');
    let msg = await wirtePath('./2.txt',content);
   console.log(msg)
}() // 這裏是自執行函數

手寫Promise

咱們以爲與其看各類各樣的Promise面試題,都不如先實現一下Promise,明白了原理,天然更容易知道怎麼用 如今,咱們開始這篇文章的壓軸代碼,實現一個簡單的Promise,

Promise的聲明

首先,Promise確定是一個類,咱們就用 class 來聲明

  • 因爲 new Promise((resolve,reject) => {}),因此傳入的參數是一個函數,咱們叫他 executor ,傳入就執行
  • executor 裏面有兩個參數 , 一個叫 resolve(成功),一個叫 reject(失敗)
  • 因爲 resolve 和 reject 可執行 , 因此都是函數 , 咱們用let 聲明

    class Promise {

    constructor(executor) {
        // 成功
        let resolve = (value) => {};
        // 失敗
        let reject = (reason) => {};
    }

    }

解決基本狀態

對Promise的規定

  • Promise存在三種狀態 (state) pending ,fulfilled ,rejected
  • pending (等待狀態)爲初始狀態 ,並能夠轉化爲 fulfilled(成功狀態) 和 rejected(失敗狀態)
  • 成功時 , 不可轉爲其餘狀態 , 而且必須有一個不可改變的值 (value)
  • 失敗時 , 不可轉爲其餘狀態 , 而且必須有一個不可改變的緣由(reason)
  • new Promise((resolve, reject)=>{resolve(value)}) resolve爲成功,接收參數value,狀態改變爲fulfilled,不可再次改變。
  • new Promise((resolve, reject)=>{reject(reason)}) reject爲失敗,接收參數reason,狀態改變爲rejected,不可再次改變。
  • 如果executor函數報錯 直接執行reject();

    class Promise {

    constructor(executor) {
        //初始化 state 爲等待狀態
        this.state = 'pending';
        // 成功的值
        this.value = undefined;
        // 失敗的值
        this.reason = undefined;
        let resolve = (value) => {
            // state 改變 , resolve 調用會失敗
            if(this.state === 'pending') {
                this.state = 'fulfilled';// 調用 resolve後,state轉化爲成功狀態
                this.value = value;// 存儲成功的值
                
            }
        };
        // 失敗
        let reject = (reason) => {
            //state 改變後, reject 調用就會失敗
            if(this.state === 'pending') {
                // reject 調用後, state 轉化爲失敗狀態
                this.state = 'rejected';
                this.reason = reason;// 存儲失敗的緣由
            }
        };
        // 當即執行
        // 若是 executor 執行報錯 , 直接執行 reject
        try {
            executor(resolve,reject);
        } catch (err) {
            reject(err);
        }
    }

    }

then方法

規定 : Promise有一個叫作then的方法 , 裏面有兩個參數 :onFulfilled , onRejected ,成功有成功的值 ,失敗有失敗的緣由

  • 當狀態state爲 fulfilled ,則執行 onFulfilled , 傳入this.value .當狀態 state爲 rejected , 則執行 onRejected , 傳入this.reason
  • onFulfilled, onRejected 若是他們是函數, 則必須分別在 fulfilled , rejected 後被調用 , value 或 reason 依次做爲他們的第一個參數

    then(onFulfilled,onRejected) {

    // 狀態爲 fulfilled , 執行 onFulfilled , 傳入成功的值
    if(this.state === 'fulfilled') {
        onFulfilled(this.value);
    };
    // 狀態爲rejected , 執行onRejected , 傳入失敗的緣由
    if(this.state === 'rejected') {
        onRejected(this.reason);
    }

    }

解決異步問題

如今基本能夠實現簡單的同步代碼 ,可是當 resolve在setTimeout 內執行 , then 時state仍是pending等待狀態 , 咱們就須要在 then調用的時候 , 將成功和失敗存到各自的數組 , 一旦 reject 或者 resolve ,就調用他們。
相似於發佈訂閱 , 先將then裏面的兩個函數存儲起來 , 因爲一個 Promise能夠有多個 then , 因此存在同一個數組內。

// 手寫Promise
class Promise {
    constructor(executor) {
        //初始化狀態
        this.state = 'pending';
        // 成功值
        this.value = undefined;
        // 失敗值
        this.reason = undefined;
        // 成功存放的數組
        this.onResolvedCallbacks = [];
        // 失敗存放的數組
        this.onRejectedCallbacks = [];
        // 成功
        let resolve = (value) => {
            // state 改變 , resolve 調用
            if(this.state === 'pending') {
                this.state = 'fulfilled';// 調用 resolve 後 , state轉換爲成功狀態
                this.value = value;//存儲成功的值
                // 一旦resolve 執行, 調用成功數組的函數
                this.onResolvedCallbacks.forEach(fn => fn());
            }
        }
        // 失敗
        let reject = (reason) => {
            if(this.state === 'pending') {
                this.state = 'rejected';
                this.reason = reason;
                // 一旦 resolve執行 , 調用失敗數組的函數
                this.onRejectedCallbacks.forEach(fn => fn());
            }
        }
        try {
            executor(resolve,reject);
        } catch (error) {
            reject(error)
        }
    }
    then(onFulfilled,onRejected) {
        // 狀態爲 fulfilled , 執行 onFulfiiled , 傳入成功的值
        if(this.state === 'fulfilled') {
            onFulfilled(this.value);
        };
        //狀態爲 rejected , 執行onRejected , 傳入失敗的緣由
        if(this.state === 'rejected') {
            onRejected(this.reason);
        }
        // 當狀態爲 pending時
        if(this.state === 'pending') {
            // onFulfilled 傳入到成功數組中
            this.onResolvedCallbacks.push(()=>{
                onFulfilled(this.value);
            })
            // onRejected 傳入到失敗數組
            this.onRejectedCallbacks.push(()=>{
                onRejected(this.reason);
            })
        }
    }
    catch() {

    }
}

解決鏈式調用

咱們經常用到的 new Promise().then().then(),這就是鏈式調用,用來解決回調地獄的問題。
  1. 爲了達成鏈式 , 咱們默認在第一個then裏返回一個Promise.

規定 : 在then裏面返回一個新的Promise , 稱爲promise2;

promise2 = new Promise((resolve,reject)=>{})

  • 將這個promise2 返回的值傳遞到下一個then中
  • 若是返回一個普通的值 , 則將普通的值傳遞給下一個then中
  1. 當咱們在第一個then中 return 了一個參數(參數未知,須要判斷)。這個return 出來的新的promise就是 onFulfilled()或 onRejected()的值。

規定 : onFulfilled()或 onRejected()的值 , 即第一個then返回的值 , 叫作 x ,判斷 x 的函數叫作 resolvePromise

  • 首先 , 要看 x 是否是 promise
  • 若是是promise , 則取它的結果 , 做爲新的 promise2
  • 若是是普通值 , 直接做爲 promise2 成功的結果
  • 因此要比較 x 和 promise2
  • resolvePromise 的參數有 promise2 (默認返回的 promise) , x(咱們本身 return 的對象) , resolve , reject
  • resolve 和 reject 是promise2的

    class Promise {

    constructor(executor) {
        //初始化狀態
        this.state = 'pending';
        // 成功值
        this.value = undefined;
        // 失敗值
        this.reason = undefined;
        // 成功存放的數組
        this.onResolvedCallbacks = [];
        // 失敗存放的數組
        this.onRejectedCallbacks = [];
        // 成功
        let resolve = (value) => {
            // state 改變 , resolve 調用
            if (this.state === 'pending') {
                this.state = 'fulfilled';// 調用 resolve 後 , state轉換爲成功狀態
                this.value = value;//存儲成功的值
                // 一旦resolve 執行, 調用成功數組的函數
                this.onResolvedCallbacks.forEach(fn => fn());
            }
        }
        // 失敗
        let reject = (reason) => {
            if (this.state === 'pending') {
                this.state = 'rejected';
                this.reason = reason;
                // 一旦 resolve執行 , 調用失敗數組的函數
                this.onRejectedCallbacks.forEach(fn => fn());
            }
        }
        try {
            executor(resolve, reject);
        } catch (error) {
            reject(error)
        }
    }
    then(onFulfilled, onRejected) {
        // 聲明返回的 promise2
        let promise2 = new Promise((resolve, reject) => {
            // 狀態爲 fulfilled , 執行 onFulfiiled , 傳入成功的值
            if (this.state === 'fulfilled') {
                let x = onFulfilled(this.value);
                // resolvePromise 函數 , 處理本身 return 的 promise和 默認的 promise2的關係
                resolvePromise(promise2, x, resolve, reject);
            };
            //狀態爲 rejected , 執行onRejected , 傳入失敗的緣由
            if (this.state === 'rejected') {
                let x = onRejected(this.reason);
                resolvePromise(promise2, x, resolve, reject);
            }
            // 當狀態爲 pending時
            if (this.state === 'pending') {
                // onFulfilled 傳入到成功數組中
                this.onResolvedCallbacks.push(() => {
                    let x = onFulfilled(this.value);
                    resolvePromise(promise2,x,resolve,reject);
                })
                // onRejected 傳入到失敗數組
                this.onRejectedCallbacks.push(() => {
                    let x = onRejected(this.reason);
                    resolvePromise(promise2,x,resolve,reject);
                })
            }
        });
        // 返回promise2 , 完成鏈式
        return promise2;
    }

    }

完成 resolvePromise函數

規定 : 一段代碼 , 讓不一樣的promise代碼互相套用 , 叫作resolvePromise

  • 若是 x === promise2 , 則是會形成循環引用 , 本身等待本身完成 ,則報 "循環引用" 錯誤。

    function resolvePromise(promise2,x,resolve,reject) {

    if(x === promise2) {
        return reject(new TypeError('Chaining cycle detected for promise'));
    }

    }

  1. 判斷 x
  • 若是 x 是一個對象或者函數 , 則設爲 x
  • x 不能是 null
  • x 是普通值 , 則直接 resolve(x)
  • x 是對象或者函數 (包括promise), let then = x.then;
  • 聲明瞭 then
  • 若是取 then 報錯 , 則走 reject()
  • 若是 then是個函數 , 則用 call執行then , 第一個參數是this ,後面是成功的回調和失敗的回調
  • 若是是成功的回調仍是promise ,就繼續遞歸
  • 成功和失敗只能調用一個 , 因此設定一個 called來防止屢次調用。

note : 這個方法是 Promise 原碼的精髓 , 能夠仔細看看

// 完成 resolvePromise函數
    function resolvePromise(promise2, x, resolve, reject) {
        // 循環引用報錯
        if (x === promise2) {
            return reject(new TypeError('Chaining cycle detected for promise'));
        }
        //防止屢次調用
        let called;
        // x不是null 而且 x是對象或者函數
        if (x != null && (typeof x === 'object' || typeof x === 'function')) {
            try {
                // A+ 規定 , 聲明 then = x 的 then 方法
                let then = x.then;
                //若是 then 是函數 , 就默認是promise 了
                if (typeof then === 'function') {
                    // 就讓 then 執行 第一個參數是 this 後面是成功的回調 和失敗的回調
                    then.call(x, y => {
                        // 成功和失敗只能調用一個
                        if (called) return;
                        called = true;
                        // resolve 就結果依舊是 promise 那就繼續解析
                        resolvePromise(promise2, y, resolve, reject);
                    }, err => {
                        // 成功和失敗只能調用一個
                        if (called) return;
                        called = true;
                        reject(err);
                    })
                } else {
                    resolve(x); // 直接成功便可
                }
            } catch (e) {
                // 也屬於失敗
                if(called) return;
                called = true;
                reject(e);
            }
        } else {
            resolve(x);
        }
    }

其餘問題

  1. 規定 : onFulfilled , onRejected 都是可選參數 , 若是他們不是函數 , 必須被忽略
  • onFulfilled返回一個普通的值 , 成功時直接等於 value => value
  • onRejected 返回一個普通的值 , 失敗時若是直接等於 value => value, 則會跑到下一個 then中的 onFulfilled 中 , 因此直接扔出一個錯誤 err => throw err
  1. 規定 onFulfilled 或 onRejected 不能同步被調用 , 必須異步調用。咱們就用 setTimeout解決異步問題
  2. 若是onFulfilled 或 onRejected報錯 , 則直接返回 reject()

最終版本

能夠 look a look

// 手寫Promise
class Promise {
    constructor(executor) {
        // 初始化state 爲等待狀態
        this.state = 'pending';
        // 成功的值
        this.value = undefined;
        // 失敗的值
        this.reason = undefined;
        // 成功存放的數組
        this.onResolvedCallbacks = [];
        // 失敗存放的數組
        this.onRejectedCallbacks = [];
        // 成功
        let resolve = (value) => {
            // state 改變 , resolve 調用的會失敗
            if (this.state === 'pending') {
                this.state = 'fulfilled'; // resolve調用後 , state轉化爲成功態
                this.value = value;// 存儲成功的值
                // 一旦resolve執行 , 調用成功數組的函數
                this.onResolvedCallbacks.forEach(fn => fn());
            }
        }
        // 失敗
        let reject = (reason) => {
            // state改變後 , reject調用就會失敗
            if (this.state === 'pending') {
                // reject 調用後 , state 轉換爲失敗狀態
                this.state = 'rejected';
                this.reason = reason;// 存儲失敗的緣由
                // 一旦reject 執行 , 調用失敗數組的函數
                this.onRejectedCallbacks.forEach(fn => fn());
            }
        };
        // 當即執行
        // 若是 executor 執行報錯 , 直接執行 reject
        try {
            executor(resolve, reject)
        } catch (err) {
            reject(err)
        }
    }
    then(onFulfilled, onRejected) {
        // onFulfilled 若是不是函數 , 就忽略 onFulfilled , 直接返回 value
        onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value;
        // onRejected 若是不是函數 , 就忽略 onRejected , 直接扔出錯誤
        onRejected = typeof onRejected === "function" ? onRejected : err => { throw err };
        // 聲明返回的 promise2
        let promise2 = new Promise((resolve, reject) => {
            if (this.state === 'fulfilled') {
                // 異步
                setTimeout(() => {
                    try {
                        let x = onFulfilled(this.value);
                        // resolvePromise函數 , 處理本身return 的promise和默認的promise2的關係
                        resolvePromise(promise2, x ,resolve,reject);
                    } catch (e) {
                        reject(e);
                    }
                },0)
            };
            if (this.state === 'rejected') {
                //異步
                setTimeout(() => {
                    // 若是報錯
                    try {
                        let x = onRejected(this.reason);
                        resolvePromise(promise2,x,resolve,reject);
                    } catch (e) {
                        reject(e);
                    }
                },0);
            };
            // 當狀態state爲pending 時
            if (this.state === 'pending') {
                // onFulfilled傳入到成功數組
                this.onResolvedCallbacks.push(() => {
                    //異步
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value);
                            resolvePromise(promise2,x , resolve,reject);
                        } catch (e) {
                            reject(e)
                        }
                    },0);
                })
                // onRejected 傳入到失敗數組
                this.onRejectedCallbacks.push(() => {
                    // 異步
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason);
                            resolvePromise(promise2,x,resolve,reject);
                        } catch (e) {
                            reject(e)
                        }
                    })
                })
            }
        });
        // 返回promise , 完成鏈式
        return promise2;
    }
    catch(fn) {
        return this.then(null,fn);
    }
}


function resolvePromise(promise2, x, resolve, reject) {
    if (x === promise2) {
        return reject(new TypeError("Chaining cycle detected for promise"));
    }
    // 防止屢次調用
    let called;
    // x 不是 null 而且 x 是對象或者函數
    if (x != null && (typeof x === 'object' || typeof x === 'function')) {
        try {
            // A+ 規定 , 聲明 then = x 的then 方法
            let then = x.then;
            // 若是 then 是函數 , 就默認是 promise了
            if (typeof then === 'function') {
                // 就讓 then 執行 第一個參數是 this 後面是成功的回調和 失敗的回調
                then.call(x, y => {
                    // 成功和失敗只能調用一個
                    if (called) return;
                    called = true;
                    // resolve 的結果依舊是 promise 那就繼續解析
                    resolvePromise(promise2, y, resolve, reject);
                }, err => {
                    // 成功和失敗只能調用一個
                    if (called) return;
                    called = true;
                    reject(err); // 失敗了就失敗了
                })
            } else {
                resolve(x); //直接成功便可
            }
        } catch (e) {
            // 也屬於失敗
            if (called) return;
            called = true;
            // 取then出錯了 那就不要再繼續執行了
            reject(e);
        }
    } else {
        resolve(x);
    }
}

固然, 這裏還有一些掛在 Promise上的方法

// resolve方法
Promise.resolve = function(val) {
    return new Promise((resolve,reject)=>{
      resolve(val)  
    })
}
// reject方法
Promise.reject = function(val) {
    return new Promise((resolve,reject)=> {
        reject(val);
    })
}
// all 方法 (獲取全部的promise , 都執行then , 把結果放到數組 , 一塊兒返回, 若是其中有一個發送了reject , 
則取出失敗的時候最早被reject失敗狀態的值)
// 這個方法比較經常使用 , 通常用於 併發請求數據 ,ajax併發請求數據
Promise.all = function(promises) {
    let arr = [];
    let i = 0;
    functionData(index,data) {
        arr[index] = data;
        i++;
        if(i == promises.length) {
            resolve(arr);
        }
    }
    return new Promise((resolve,reject) => {
        for(let i = 0 ; i < promises.length ; i ++) {
            promises[i].then(data => {
                processData(i,data);
            },reject)
        }
    })
}
// race方法
// mdn的解釋:
Promise.race()一旦可迭代的promise中的一個承諾實現或拒絕,該方法就會返回一個承諾,該承諾中包含承諾的價值或緣由。
// 仍是比較好理解的
Promise.race = function(promises) {
    return new Promise((resolve,reject) =>{
        for(let i = 0 ; i < promises.length ; i++) {
            promises[i].then(resolve,reject);
        }
    })
}

// 用法大概是下面這樣了
    let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('成功了')
    }, 1000);
  })
  
  let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('success')    
    }, 900);
  })
  
  
  Promise.race([p1, p2]).then((result) => {
    console.log(result) // success
  })

參考資料

  1. 《深刻了解ES6》,電子工業出版社
  2. BAT前端經典面試問題:史上最最最詳細的手寫Promise教程

最後

相關文章
相關標籤/搜索