淺析Promise原理

a1322674015發表於2019-10-20

一、Promise基礎用法

1.1 基本用法


new 
Promise(

function(
resolve, reject) {

    //待處理的非同步邏輯
    //處理結束後,呼叫resolve或reject方法
})
  • 新建一個 promise很簡單,只需要 new一個 promise物件即可。所以 promise本質上就是一個函式,它接受一個函式作為引數,並且會返回 promise物件,這就給鏈式呼叫提供了基礎
  • 其實 Promise函式的使命,就是構建出它的例項,並且負責幫我們管理這些例項。而這些例項有以下三種狀態:
  • pending: 初始狀態,位履行或拒絕
  • fulfilled: 意味著操作成功完成
  • rejected: 意味著操作失敗

pending 狀態的  Promise物件可能以  fulfilled狀態返回了一個值,也可能被某種理由(異常資訊)拒絕( reject)了。當其中任一種情況出現時, Promise 物件的  then 方法繫結的處理方法(handlers)就會被呼叫,then方法分別指定了 resolve方法和 reject方法的回撥函式


var promise = 
new 
Promise(

function(
resolve, reject) {

  if ( /* 非同步操作成功 */){
   resolve(value);
 } else {
   reject(error);
 }
});
promise.then( function( value) {
  // 如果呼叫了resolve方法,執行此函式
}, function( value) {
  // 如果呼叫了reject方法,執行此函式
});

上述程式碼很清晰的展示了 promise物件執行的機制。下面再看一個示例:


var getJSON = 

function(
url) {

  var promise = new Promise( function( resolve, reject){
    var client = new XMLHttpRequest();
   client.open( "GET", url);
   client.>
   client.responseType = "json";
   client.setRequestHeader( "Accept", "application/json");
   client.send();
    function handler( ) {
      if ( this.status === 200) {
             resolve( this.response);
         } else {
             reject( new Error( this.statusText));
         }
   };
 });
  return promise;
};
getJSON( "/posts.json").then( function( json) {
  console.log( 'Contents: ' + json);
}, function( error) {
  console.error( '出錯了', error);
});

上面程式碼中, resolve方法和 reject方法呼叫時,都帶有引數。它們的引數會被傳遞給回撥函式。 reject方法的引數通常是 Error物件的例項,而 resolve方法的引數除了正常的值以外,還可能是另一個 Promise例項,比如像下面這樣。


var p1 = 
new 
Promise(

function(
resolve, reject){

  // ... some code
});
var p2 = new Promise( function( resolve, reject){
  // ... some code
 resolve(p1);
})

上面程式碼中, p1p2都是 Promise的例項,但是 p2resolve方法將 p1作為引數,這時 p1的狀態就會傳遞給 p2。如果呼叫的時候, p1的狀態是 pending,那麼 p2的回撥函式就會等待 p1的狀態改變;如果 p1的狀態已經是 fulfilled或者 rejected,那麼 p2的回撥函式將會立刻執行

1.2 promise捕獲錯誤

Promise.prototype.catch方法是 Promise.prototype.then(null, rejection)的別名,用於指定發生錯誤時的回撥函式

getJSON(
"/visa.json").then(

function(
result) {

  // some code
}).catch( function( error) {
  // 處理前一個回撥函式執行時發生的錯誤
  console.log( '出錯啦!', error);
});

Promise物件的錯誤具有“冒泡”性質,會一直向後傳遞,直到被捕獲為止。也就是說,錯誤總是會被下一個 catch語句捕獲

getJSON(
"/visa.json").then(

function(
json) {

  return json.name;
}).then( function( name) {
  // proceed
}).catch( function( error) {
    //處理前面任一個then函式丟擲的錯誤
});

1.3 常用的promise方法

Promise.all方法

Promise.all方法用於將多個 Promise例項,包裝成一個新的 Promise例項


var p = 
Promise.all([p1,p2,p3]);

  • 上面程式碼中, Promise.all方法接受一個陣列作為引數, p1p2p3都是 Promise物件的例項。( Promise.all方法的引數不一定是陣列,但是必須具有 iterator介面,且返回的每個成員都是 Promise例項。)

p的狀態由 p1p2p3決定,分成兩種情況

  • 只有 p1p2p3的狀態都變成 fulfilledp的狀態才會變成 fulfilled,此時 p1p2p3的返回值組成一個陣列,傳遞給 p的回撥函式
  • 只要 p1p2p3之中有一個被 rejectedp的狀態就變成 rejected,此時第一個被 reject的例項的返回值,會傳遞給p的回撥函式

// 生成一個Promise物件的陣列

var promises = [ 2, 3, 5, 7, 11, 13].map( function( id){
  return getJSON( "/get/addr" + id + ".json");
});
Promise.all(promises).then( function( posts) {
  // ...  
}).catch( function( reason){
  // ...
});

Promise.race方法

Promise.race方法同樣是將多個 Promise例項,包裝成一個新的 Promise例項。


var p = 
Promise.race([p1,p2,p3]);

上面程式碼中,只要 p1p2p3之中有一個例項率先改變狀態,p的狀態就跟著改變。那個率先改變的Promise例項的返回值,就傳遞給p的返回值

  • 如果 Promise.all方法和 Promise.race方法的引數,不是 Promise例項,就會先呼叫下面講到的 Promise.resolve方法,將引數轉為 Promise例項,再進一步處理

Promise.resolve

有時需要將現有物件轉為 Promise物件, Promise.resolve方法就起到這個作用


var jsPromise = 
Promise.resolve($.ajax(
'/whatever.json'));

上面程式碼將 jQuery生成 deferred物件,轉為一個新的 ES6Promise物件

  • 如果 Promise.resolve方法的引數,不是具有 then方法的物件(又稱 thenable物件),則返回一個新的 Promise物件,且它的狀態為 fulfilled

var p = 
Promise.resolve(
'Hello');

p.then( function ( s){
  console.log(s)
});
// Hello
  • 上面程式碼生成一個新的 Promise物件的例項 p,它的狀態為 fulfilled,所以回撥函式會立即執行, Promise.resolve方法的引數就是回撥函式的引數
  • 如果 Promise.resolve方法的引數是一個 Promise物件的例項,則會被原封不動地返回
  • Promise.reject(reason)方法也會返回一個新的 Promise例項,該例項的狀態為 rejectedPromise.reject方法的引數 reason,會被傳遞給例項的回撥函式

var p = 
Promise.reject(
'出錯啦');

p.then( null, function ( error){
  console.log(error)
});
// 出錯了

1.4 Async/await簡化寫法



function 
getDataAsync (
url) {

    return new Promise( ( resolve, reject) => {
       setTimeout( () => {
            var res = {
               url: url,
               data: Math.random()
           }
           resolve(res)
       }, 1000)
   })
}

async 

function 
getData (
) {

    var res1 = await getDataAsync( '/page/1?param=123')
    console.log(res1)
    var res2 = await getDataAsync( `/page/2?param= ${res1.data}`)
    console.log(res2)
    var res3 = await getDataAsync( `/page/2?param= ${res2.data}`)
    console.log(res3)
}

async/await 是基於  Promise 的,因為使用  async 修飾的方法最終返回一個  Promise, 實際上, async/await 可以看做是使用  Generator 函式處理非同步的語法糖,我們來看看如何使用  Generator 函式處理非同步

1.5 Generator

首先非同步函式依然是:



function 
getDataAsync (
url) {

    return new Promise( ( resolve, reject) => {
       setTimeout( () => {
            var res = {
               url: url,
               data: Math.random()
           }
           resolve(res)
       }, 1000)
   })
}

使用  Generator 函式可以這樣寫



function * 
getData (
) {

    var res1 = yield getDataAsync( '/page/1?param=123')
    console.log(res1)
    var res2 = yield getDataAsync( `/page/2?param= ${res1.data}`)
    console.log(res2)
    var res3 = yield getDataAsync( `/page/2?param= ${res2.data}`)
    console.log(res3))
}

然後我們這樣逐步執行


var g = getData()

g.next().value.then( res1 => {
   g.next(res1).value.then( res2 => {
       g.next(res2).value.then( () => {
           g.next()
       })
   })
})

上面的程式碼,我們逐步呼叫遍歷器的  next() 方法,由於每一個  next() 方法返回值的  value 屬性為一個  Promise 物件,所以我們為其新增  then 方法, 在  then方法裡面接著執行  next 方法挪移遍歷器指標,直到  Generator 函式執行完成,實際上,這個過程我們不必手動完成,可以封裝成一個簡單的執行器



function 
run (
gen) {

    var g = gen()

    function next ( data) {
        var res = g.next(data)
        if (res.done) return res.value
       res.value.then( ( data) => {
           next(data)
       })
   }

   next()

}

run方法用來自動執行非同步的  Generator 函式,其實就是一個遞迴的過程呼叫的過程。這樣我們就不必手動執行  Generator 函式了。 有了  run 方法,我們只需要這樣執行 getData 方法

run(getData)

這樣,我們就可以把非同步操作封裝到  Generator 函式內部,使用  run 方法作為  Generator 函式的自執行器,來處理非同步。其實我們不難發現,  async/await 方法相比於  Generator 處理非同步的方式,有很多相似的地方,只不過  async/await 在語義化方面更加明顯,同時  async/await 不需要我們手寫執行器,其內部已經幫我們封裝好了,這就是為什麼說  async/await 是  Generator 函式處理非同步的語法糖了

二、Promise實現原理剖析

2.1 Promise標準

  • Promise 規範有很多,如 Promise/APromise/BPromise/D以及  Promise/A 的升級版  Promise/A+ES6中採用了  Promise/A+ 規範

中文版規範:  Promises/A+規範(中文)

Promise標準解讀

  • 一個 promise的當前狀態只能是 pendingfulfilledrejected三種之一。狀態改變只能是 pendingfulfilled或者 pendingrejected。狀態改變不可逆
  • promisethen方法接收兩個可選引數,表示該 promise狀態改變時的回撥( promise.then(onFulfilled, onRejected))。 then方法返回一個 promisethen 方法可以被同一個  promise 呼叫多次

2.2 實現Promise

建構函式



function 
Promise(
resolver) {}

原型方法


Promise.prototype.then = 

function(
) {}

Promise.prototype.catch = function( ) {}

靜態方法


Promise.resolve = 

function(
) {}

Promise.reject = function( ) {}
Promise.all = function( ) {}
Promise.race = function( ) {}

2.3 極簡promise雛形



function 
Promise(
fn) {

    var value = null,
       callbacks = [];   //callbacks為陣列,因為可能同時有很多個回撥
    this.then = function ( onFulfilled) {
       callbacks.push(onFulfilled);
   };
    function resolve( value) {
       callbacks.forEach( function ( callback) {
           callback(value);
       });
   }
   fn(resolve);
}

大致的邏輯是這樣的

  • 呼叫 then方法,將想要在 Promise非同步操作成功時執行的回撥放入 callbacks佇列,其實也就是註冊回撥函式,可以向觀察者模式方向思考
  • 建立 Promise例項時傳入的函式會被賦予一個函式型別的引數,即 resolve,它接收一個引數 value,代表非同步操作返回的結果,當一步操作執行成功後,使用者會呼叫 resolve方法,這時候其實真正執行的操作是將 callbacks佇列中的回撥一一執行

//例1

function getUserId( ) {
    return new Promise( function( resolve) {
        //非同步請求
       http.get(url, function( results) {
           resolve(results.id)
       })
   })
}
getUserId().then( function( id) {
    //一些處理
})

// 結合例子1分析


// fn 就是getUserId函式
function Promise( fn) {
    var value = null,
       callbacks = [];   //callbacks為陣列,因為可能同時有很多個回撥
   
    // 當使用者呼叫getUserId().then的時候開始註冊傳進來的回撥函式
    // onFulfilled就是例子中的function(id){}
    // 把then的回撥函式收集起來 在resolve的時候呼叫
    this.then = function ( onFulfilled) {
       callbacks.push(onFulfilled);
   };
   
    // value是fn函式執行後返回的值
    function resolve( value) {
        // callbacks是傳給then的回撥函式就是例子中的function(id){}
        // 遍歷使用者通過then傳遞進來的回撥函式把resolve成功的結果返回給then呼叫即then(function(data){ console.log(data) }) 這裡的data就是通過這裡呼叫返回
       callbacks.forEach( function ( callback) {
           callback(value);
       });
   }
   
    //執行fn函式即getUserId()並且傳入函式引數resolve 當fn執行完成返回的值傳遞給resolve函式
   fn(resolve);
}

結合例1中的程式碼來看,首先 new Promise時,傳給 promise的函式傳送非同步請求,接著呼叫 promise物件的 then屬性,註冊請求成功的回撥函式,然後當非同步請求傳送成功時,呼叫 resolve(results.id)方法, 該方法執行 then方法註冊的回撥陣列

  • then方法應該能夠鏈式呼叫,但是上面的最基礎簡單的版本顯然無法支援鏈式呼叫。想讓 then方法支援鏈式呼叫,其實也是很簡單的

this.then = 

function (
onFulfilled) {

   callbacks.push(onFulfilled);
    return this;
};

只要簡單一句話就可以實現類似下面的鏈式呼叫


// 例2

getUserId().then( function ( id) {
    // 一些處理
}).then( function ( id) {
    // 一些處理
});

2.4 加入延時機制

上述程式碼可能還存在一個問題:如果在 then方法註冊回撥之前, resolve函式就執行了,怎麼辦?比如 promise內部的函式是同步函式


// 例3

function getUserId( ) {
    return new Promise( function ( resolve) {
       resolve( 9876);
   });
}
getUserId().then( function ( id) {
    // 一些處理
});

這顯然是不允許的, Promises/A+規範明確要求回撥需要通過非同步方式執行,用以保證一致可靠的執行順序。因此我們要加入一些處理,保證在 resolve執行之前, then方法已經註冊完所有的回撥。我們可以這樣改造下 resolve函式:



function 
resolve(
value) {

   setTimeout( function( ) {
       callbacks.forEach( function ( callback) {
           callback(value);
       });
   }, 0)
}

上述程式碼的思路也很簡單,就是通過 setTimeout機制,將 resolve中執行回撥的邏輯放置到 JS任務佇列末尾,以保證在 resolve執行時, then方法的回撥函式已經註冊完成

  • 但是,這樣好像還存在一個問題,可以細想一下:如果 Promise非同步操作已經成功,這時,在非同步操作成功之前註冊的回撥都會執行,但是在 Promise非同步操作成功這之後呼叫的 then註冊的回撥就再也不會執行了,這顯然不是我們想要的

2.5 加入狀態

我們必須加入狀態機制,也就是大家熟知的 pendingfulfilledrejected

Promises/A+規範中的 2.1 Promise States中明確規定了, pending可以轉化為 fulfilledrejected並且只能轉化一次,也就是說如果 pending轉化到 fulfilled狀態,那麼就不能再轉化到r ejected。並且 fulfilledrejected狀態只能由 pending轉化而來,兩者之間不能互相轉換


//改進後的程式碼是這樣的:


function Promise( fn) {
    var state = 'pending',
       value = null,
       callbacks = [];
    this.then = function ( onFulfilled) {
        if (state === 'pending') {
           callbacks.push(onFulfilled);
            return this;
       }
       onFulfilled(value);
        return this;
   };
    function resolve( newValue) {
       value = newValue;
       state = 'fulfilled';
       setTimeout( function ( ) {
           callbacks.forEach( function ( callback) {
               callback(value);
           });
       }, 0);
   }
   fn(resolve);
}

上述程式碼的思路是這樣的: resolve執行時,會將狀態設定為 fulfilled,在此之後呼叫 then新增的新回撥,都會立即執行

2.6 鏈式Promise

如果使用者在 then函式裡面註冊的仍然是一個 Promise,該如何解決?比如下面的例4


// 例4

getUserId()
   .then(getUserJobById)
   .then( function ( job) {
        // 對job的處理
   });
function getUserJobById( id) {
    return new Promise( function ( resolve) {
       http.get(baseUrl + id, function( job) {
           resolve(job);
       });
   });
}
  • 這種場景相信用過 promise的人都知道會有很多,那麼類似這種就是所謂的鏈式 Promise
  • 鏈式 Promise是指在當前 promise達到 fulfilled狀態後,即開始進行下一個 promise(後鄰 promise)。那麼我們如何銜接當前 promise和後鄰 promise呢?(這是這裡的難點
  • 只要在 then方法裡面 return一個 promise就好啦。 Promises/A+規範中的 2.2.7就是這樣

下面來看看這段暗藏玄機的 then方法和 resolve方法改造程式碼



function 
Promise(
fn) {

    var state = 'pending',
       value = null,
       callbacks = [];
    this.then = function ( onFulfilled) {
        return new Promise( function ( resolve) {
           handle({
               onFulfilled: onFulfilled || null,
               resolve: resolve
           });
       });
   };
    function handle( callback) {
        if (state === 'pending') {
           callbacks.push(callback);
            return;
       }
        //如果then中沒有傳遞任何東西
        if(!callback.onFulfilled) {
           callback.resolve(value);
            return;
       }
        var ret = callback.onFulfilled(value);
       callback.resolve(ret);
   }
   
    function resolve( newValue) {
        if (newValue && ( typeof newValue === 'object' || typeof newValue === 'function')) {
            var then = newValue.then;
            if ( typeof then === 'function') {
               then.call(newValue, resolve);
                return;
           }
       }
       state = 'fulfilled';
       value = newValue;
       setTimeout( function ( ) {
           callbacks.forEach( function ( callback) {
               handle(callback);
           });
       }, 0);
   }
   fn(resolve);
}

我們結合例4的程式碼,分析下上面的程式碼邏輯,為了方便閱讀,我把例4的程式碼貼在這裡


// 例4

getUserId()
   .then(getUserJobById)
   .then( function ( job) {
        // 對job的處理
   });
function getUserJobById( id) {
    return new Promise( function ( resolve) {
       http.get(baseUrl + id, function( job) {
           resolve(job);
       });
   });
}
  • then方法中,建立並返回了新的 Promise例項,這是序列 Promise的基礎,並且支援鏈式呼叫
  • handle方法是 promise內部的方法。 then方法傳入的形參 onFulfilled以及建立新 Promise例項時傳入的 resolve均被 push到當前 promisecallbacks佇列中,這是銜接當前 promise和後鄰 promise的關鍵所在
  • getUserId生成的 promise(簡稱 getUserId promise)非同步操作成功,執行其內部方法 resolve,傳入的引數正是非同步操作的結果 id
  • 呼叫 handle方法處理 callbacks佇列中的回撥: getUserJobById方法,生成新的 promise(g etUserJobById promise
  • 執行之前由 getUserId promisethen方法生成的新 promise(稱為 bridge promise)的 resolve方法,傳入引數為 getUserJobById promise。這種情況下,會將該 resolve方法傳入 getUserJobById promisethen方法中,並直接返回
  • getUserJobById promise非同步操作成功時,執行其 callbacks中的回撥: getUserId bridge promise中的 resolve方法
  • 最後執行 getUserId bridge promise的後鄰 promisecallbacks中的回撥

2.7 失敗處理

在非同步操作失敗時,標記其狀態為 rejected,並執行註冊的失敗回撥


//例5

function getUserId( ) {
    return new Promise( function( resolve) {
        //非同步請求
       http.get(url, function( error, results) {
            if (error) {
               reject(error);
           }
           resolve(results.id)
       })
   })
}
getUserId().then( function( id) {
    //一些處理
}, function( error) {
    console.log(error)
})

有了之前處理 fulfilled狀態的經驗,支援錯誤處理變得很容易,只需要在註冊回撥、處理狀態變更上都要加入新的邏輯



function 
Promise(
fn) {

    var state = 'pending',
       value = null,
       callbacks = [];
    this.then = function ( onFulfilled, onRejected) {
        return new Promise( function ( resolve, reject) {
           handle({
               onFulfilled: onFulfilled || null,
               onRejected: onRejected || null,
               resolve: resolve,
               reject: reject
           });
       });
   };
    function handle( callback) {
        if (state === 'pending') {
           callbacks.push(callback);
            return;
       }
        var cb = state === 'fulfilled' ? callback.onFulfilled : callback.onRejected,
           ret;
        if (cb === null) {
           cb = state === 'fulfilled' ? callback.resolve : callback.reject;
           cb(value);
            return;
       }
       ret = cb(value);
       callback.resolve(ret);
   }
    function resolve( newValue) {
        if (newValue && ( typeof newValue === 'object' || typeof newValue === 'function')) {
            var then = newValue.then;
            if ( typeof then === 'function') {
               then.call(newValue, resolve, reject);
                return;
           }
       }
       state = 'fulfilled';
       value = newValue;
       execute();
   }
    function reject( reason) {
       state = 'rejected';
       value = reason;
       execute();
   }
    function execute( ) {
       setTimeout( function ( ) {
           callbacks.forEach( function ( callback) {
               handle(callback);
           });
       }, 0);
   }
   fn(resolve, reject);
}

上述程式碼增加了新的 reject方法,供非同步操作失敗時呼叫,同時抽出了 resolvereject共用的部分,形成 execute方法

錯誤冒泡是上述程式碼已經支援,且非常實用的一個特性。在 handle中發現沒有指定非同步操作失敗的回撥時,會直接將 bridge promise( then函式返回的 promise,後同)設為 rejected狀態,如此達成執行後續失敗回撥的效果。這有利於簡化序列Promise的失敗處理成本,因為一組非同步操作往往會對應一個實際功能,失敗處理方法通常是一致的


//例6

getUserId()
   .then(getUserJobById)
   .then( function ( job) {
        // 處理job
   }, function ( error) {
        // getUserId或者getUerJobById時出現的錯誤
        console.log(error);
   });

2.8 異常處理

如果在執行成功回撥、失敗回撥時程式碼出錯怎麼辦?對於這類異常,可以使用 try-catch捕獲錯誤,並將 bridge promise設為 rejected狀態。 handle方法改造如下



function 
handle(
callback) {

    if (state === 'pending') {
       callbacks.push(callback);
        return;
   }
    var cb = state === 'fulfilled' ? callback.onFulfilled : callback.onRejected,
       ret;
    if (cb === null) {
       cb = state === 'fulfilled' ? callback.resolve : callback.reject;
       cb(value);
        return;
   }
    try {
       ret = cb(value);
       callback.resolve(ret);
   } catch (e) {
       callback.reject(e);
   }
}

如果在非同步操作中,多次執行 resolve或者 reject會重複處理後續回撥,可以通過內建一個標誌位解決

2.9 完整實現


// 三種狀態

const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
// promise 接收一個函式引數,該函式會立即執行
function MyPromise( fn) {
  let _this = this;
 _this.currentState = PENDING;
 _this.value = undefined;
  // 用於儲存 then 中的回撥,只有當 promise
  // 狀態為 pending 時才會快取,並且每個例項至多快取一個
 _this.resolvedCallbacks = [];
 _this.rejectedCallbacks = [];

 _this.resolve = function ( value) {
    if (value instanceof MyPromise) {
      // 如果 value 是個 Promise,遞迴執行
      return value.then(_this.resolve, _this.reject)
   }
   setTimeout( () => { // 非同步執行,保證執行順序
      if (_this.currentState === PENDING) {
       _this.currentState = RESOLVED;
       _this.value = value;
       _this.resolvedCallbacks.forEach( cb => cb());
     }
   })
 };

 _this.reject = function ( reason) {
   setTimeout( () => { // 非同步執行,保證執行順序
      if (_this.currentState === PENDING) {
       _this.currentState = REJECTED;
       _this.value = reason;
       _this.rejectedCallbacks.forEach( cb => cb());
     }
   })
 }
  // 用於解決以下問題
  // new Promise(() => throw Error('error))
  try {
   fn(_this.resolve, _this.reject);
 } catch (e) {
   _this.reject(e);
 }
}

MyPromise.prototype.then = function ( onResolved, onRejected) {
  var self = this;
  // 規範 2.2.7,then 必須返回一個新的 promise
  var promise2;
  // 規範 2.2.onResolved 和 onRejected 都為可選引數
  // 如果型別不是函式需要忽略,同時也實現了透傳
  // Promise.resolve(4).then().then((value) => console.log(value))
  typeof 'function' ? onResolved : v => v;
  typeof 'function' ? onRejected : r => throw r;

  if (self.currentState === RESOLVED) {
    return (promise2 = new MyPromise( function ( resolve, reject) {
      // 規範 2.2.4,保證 onFulfilled,onRjected 非同步執行
      // 所以用了 setTimeout 包裹下
     setTimeout( function ( ) {
        try {
          var x = onResolved(self.value);
         resolutionProcedure(promise2, x, resolve, reject);
       } catch (reason) {
         reject(reason);
       }
     });
   }));
 }

  if (self.currentState === REJECTED) {
    return (promise2 = new MyPromise( function ( resolve, reject) {
     setTimeout( function ( ) {
        // 非同步執行onRejected
        try {
          var x = onRejected(self.value);
         resolutionProcedure(promise2, x, resolve, reject);
       } catch (reason) {
         reject(reason);
       }
     });
   }));
 }

  if (self.currentState === PENDING) {
    return (promise2 = new MyPromise( function ( resolve, reject) {
     self.resolvedCallbacks.push( function ( ) {
        // 考慮到可能會有報錯,所以使用 try/catch 包裹
        try {
          var x = onResolved(self.value);
         resolutionProcedure(promise2, x, resolve, reject);
       } catch (r) {
         reject(r);
       }
     });

     self.rejectedCallbacks.push( function ( ) {
        try {
          var x = onRejected(self.value);
         resolutionProcedure(promise2, x, resolve, reject);
       } catch (r) {
         reject(r);
       }
     });
   }));
 }
};
// 規範 2.3
function resolutionProcedure( promise2, x, resolve, reject) {
  // 規範 2.3.1,x 不能和 promise2 相同,避免迴圈引用
  if (promise2 === x) {
    return reject( new TypeError( "Error"));
 }
  // 規範 2.3.2
  // 如果 x 為 Promise,狀態為 pending 需要繼續等待否則執行
  if (x instanceof MyPromise) {
    if (x.currentState === PENDING) {
     x.then( function ( value) {
        // 再次呼叫該函式是為了確認 x resolve 的
        // 引數是什麼型別,如果是基本型別就再次 resolve
        // 把值傳給下個 then
       resolutionProcedure(promise2, value, resolve, reject);
     }, reject);
   } else {
     x.then(resolve, reject);
   }
    return;
 }
  // 規範 2.3.3.3.3
  // reject 或者 resolve 其中一個執行過得話,忽略其他的
  let called = false;
  // 規範 2.3.3,判斷 x 是否為物件或者函式
  if (x !== null && ( typeof x === "object" || typeof x === "function")) {
    // 規範 2.3.3.2,如果不能取出 then,就 reject
    try {
      // 規範 2.3.3.1
      let then = x.then;
      // 如果 then 是函式,呼叫 x.then
      if ( typeof then === "function") {
        // 規範 2.3.3.3
       then.call(
         x,
         y => {
            if (called) return;
           called = true;
            // 規範 2.3.3.3.1
           resolutionProcedure(promise2, y, resolve, reject);
         },
         e => {
            if (called) return;
           called = true;
           reject(e);
         }
       );
     } else {
        // 規範 2.3.3.4
       resolve(x);
     }
   } catch (e) {
      if (called) return;
     called = true;
     reject(e);
   }
 } else {
    // 規範 2.3.4,x 為基本型別
   resolve(x);
 }
}

2.10 小結

這裡一定要注意的點是promise裡面的 then函式僅僅是註冊了後續需要執行的程式碼,真正的執行是在 resolve方法裡面執行的,理清了這層,再來分析原始碼會省力的多

現在回顧下 Promise的實現過程,其主要使用了設計模式中的觀察者模式

  • 通過 Promise.prototype.thenPromise.prototype.catch方法將觀察者方法註冊到被觀察者 Promise物件中,同時返回一個新的 Promise物件,以便可以鏈式呼叫
  • 被觀察者管理內部 pendingfulfilledrejected的狀態轉變,同時通過建構函式中傳遞的 resolvereject方法以主動觸發狀態轉變和通知觀察者

三、參考


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69946034/viewspace-2660713/,如需轉載,請註明出處,否則將追究法律責任。

相關文章