再談Promise以及其實現-沒有基於Promise/A規範

Promise的前世此生

在js中,異步是一個很是重要的組成部分,它基於事件循環,保證了優先級更高任務的優先執行權,好比js下載、UI渲染、js中非異步的任務,異步使得單進程的js可以作到非阻塞,這在node顯得攸關重要,它使得js沒必要等待I/O操做返回結果,而能去處理其餘任務。可是異步也存在着缺點,最明顯的就是回地獄,而Pormise規範的出現,讓開發者從回調地獄從解放出來。它由社區提出和實現,以後被es6加入到標準當中。node

Promise的組成

Promise最基本的是由status、resolve、onResolved、reject、onRejected、then和catch幾部分組成:webpack

status:狀態管理,有pendding,fulfilled和rejected三種狀態,且狀態一經改變是沒法逆轉的;
resolve: 一個函數,當調用該函數時,說明異步任務執行成功了,promise的status由pendding轉爲fulfilled,且會調用成功的回調函數onResolved;
reject: 一個函數,當調用該函數時,說明異步任務執行失敗,promise的status由pendding轉爲rejected,且會調用失敗的回調函數onRejected;
then: 一個函數,在then的參數裏面,咱們須要定義回調成功須要執行的成功回調函數,promise會將這個成功回調函數註冊到onResolved,由resolve觸發調用,而且還支持鏈式調用;
catch: 一個函數,在reject的參數裏面,咱們須要回調失敗須要執行的失敗回調函數,promise會將這個失敗回調函數註冊到onRejected,由reject觸發調用;es6

image

備註: 這裏沒講race和all方法等,有興趣的能夠去看下文檔。web

使用方法

經過new建立一個Promise對象,傳入一個函數參數,這個函數包含resolve和reject參數,這兩個參數,如上所述也是函數,當異步任務完成的時候,調用resolve方法,當異步任務失敗的時候,調用reject方法。eg:面試

var p = new Promise(function(resolve, reject){
    setTimeout(function() {
        resolve(1)
    }, 1000)
})
複製代碼

then方法,參數也是一個回調函數,當你調用resolve方法後,這個回調函數就會調用,且resolve函數傳入的參數,會帶給這個回調函數的參數,eg:編程

p.then(function(arg){
    console.log(arg) // 1
})
複製代碼

catch方法,參數一樣是一個回調函數,當你調用reject方法後,這個回調函數就會調用,且reject函數傳入的參數,會帶給這個回調函數,使用例子和then同理。
因此咱們的Promise的調用能夠是這樣的:promise

var p = new Promise(function(resolve, reject){
    // 異步任務執行,且看結果調用resolve仍是reject
}).then(function(){
    // do something1
    return new Promise...
}).then(function() {
    // do something2
    return new Promise....
}).then(function() {
    // do something3
    return new Promise....
}).then(function() {
    // do something4
    return new Promise....
})
複製代碼

對比用回調的方式bash

doAsyncTask1(function(){
    // do something1
    doAsyncTask2(function() {
        // do something2
        doAsyncTask3(function() {
            // do something3
            doAsyncTask4(function(){
                // do something4
            })
        })
    })
    
})
複製代碼

是否是比起異步編程,promise更人性化呢?babel

async await

上面promise的調用方式其實還不夠優雅,還有更加優雅的調用方式,那就是async await方式,咱們來看下如何用。異步

async testPromise() {
   var result = await new Promise(function(resolve, reject){
       setTimeout(function(){
           resolve(1)
       }, 2000)
   })
   console.log(result)
   var result1 = await new Promise(function(resolve, reject){
       setTimeout(function(){
           resolve(1)
       }, 2000)
   })
   console.log(result1)
}
複製代碼

是否是已經變成了咱們同步的編程方式了?
這裏有兩點須要注意的:

  1. await關鍵字必須在使用async修飾的方法中才能使用,反之則沒問題。
  2. async不能直接使用,須要使用babel進行轉譯,通常須要藉助webpack進行配置,不能單獨在js中使用,這個相對來講比較麻煩。

如何實現本身實現Promise

面試官很喜歡問你如何實現一個Promise,這個時候咱們就須要對Promise有必定的理解,咱們不去看Promise/A+規範,太複雜晦澀難懂了,咱們就按照上面講的Promise的組成來實現一個簡單的Promise。

function _Promise(fn) {
    this.status = 'pending';
    this.onResolved = null;
    this.onRejected = null;
    
    fn.call(this, this.resolve.bind(this), this.reject.bind(this))
    
}

_Promise.prototype.resolve = function(arg) {
    if(this.status === 'pending') {
        this.onResolved(arg)
        this.status = 'fulfilled'
    }
}
_Promise.prototype.reject = function(arg) {
    if(this.status === 'pending') {
        this.onRejected(arg)
        this.status = 'rejected'
    }
}

_Promise.prototype.then = function(onResolved) {
    this.onResolved = onResolved
}

_Promise.prototype.reject = function(onRejected) {
    this.onResolved = onRejected
}
複製代碼

這就是一個最簡單的Promise實現方式,可是它還不支持鏈式調用。因此咱們須要在then方法再返回一個Promise, 咱們改造一下:

function _Promise(fn) {
    this.status = 'pending';
    this.onResolved = null;
    this.onRejected = null;
    this.childReject = null;
    this.childResolve = null;
    
    fn.call(this, this.resolve.bind(this), this.reject.bind(this))
    
}

_Promise.prototype.resolve = function(arg) {
    if(this.status === 'pending') {
        if(this.onResolved) {
            var ret = this.onResolved(arg)
            // 若是第二個then return的是一個用戶自定義的promise,咱們稱爲PromiseB,則須要把childResolve賦值給這個PromiseB的onResolved,交給這個PromiseB來執行
            if(ret instanceof _Promise) {
                ret.onResolved = this.childResolve
                return
            }
            // 不然直接調用childResove確保第二個then的回調執行
            this.childResolve && this.childResolve()
        }
        this.status = 'fulfilled'
    }
    
}
_Promise.prototype.reject = function(arg) {
    if(this.status === 'pending') {
        this.onRejected && this.onRejected(arg)
        this.childReject && this.childReject()
        this.status = 'rejected'
    }
}

_Promise.prototype.then = function(onResolved) {
    this.onResolved = onResolved
    var that = this
    // 定義一個promise,咱們簡稱PromiseA,以便鏈式調用
    return new _Promise(function(resolve, reject){
      // 這裏須要確保resolve方法在第一個promise的resolve調用後調用,那麼須要把它保存到第一個promise裏面
      that.childResolve = resolve
      that.childReject = reject
    })
}

_Promise.prototype.reject = function(onRejected) {
    this.onResolved = onRejected
}

// 例子
new _Promise((resolve) => {
    setTimeout(() => {
        resolve()
    }, 2000)
}).then(() => {
    console.log('promise success')
    return new _promise((resolve) => {
        setTimeout(() => {
            resolve()
        }, 1000)
    })
}).then(() => {
    console.log('promise2 success')
})

複製代碼

這個實現,有三個Promise,第一個是首個Promise,第二個PormiseA即便若是then方法沒有返回用戶自定義的Promise的時候,咱們方便鏈式調用的Promise,第三個PromiseB是then方法返回用戶自定義的Promise的時候,咱們須要把第二個then的回調交還給PromiseB執行。

相關文章
相關標籤/搜索