手寫實現簡單的promise

手寫promise幾乎每家大廠的必備考題,幾回面試都被這個問題坑了,因而花了些時間特地研究了一下,下面是promise實現的思考過程。你們若是不嫌棄,還請往下看:面試

promise的介紹

所謂Promise,簡單說就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果。從語法上說,Promise 是一個對象,從它能夠獲取異步操做的消息。 例如如下一個promise的例子:promise

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 異步操做成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});
複製代碼

能夠看出一個promise的構造函數包含兩個方法resolve、reject,同時根據promise+規範可知promise包含三個狀態:bash

  • pending: 初始狀態,既不是成功,也不是失敗狀態。
  • fulfilled: 意味着操做成功完成。
  • rejected: 意味着操做失敗。 那麼咱們能夠能夠根據這三種不一樣狀態去實現resolve、reject,以及實現then方法,那麼一個簡單的promise雛形就出來了。下面來實現它:

promise構造函數:

首先能夠根據上面的推測寫個構造函數以下:閉包

/**
 * 建立三變量記錄表示狀態
 * 用that保存this,避免後期閉包致使this的指向不對
 * value 變量用於保存 resolve 或者 reject 中傳入的值
 * resolvedCallbacks 和 rejectedCallbacks 用於保存 then 中的回調,
 * 由於當執行完 Promise 時狀態可能仍是等待中,這時候應該把 then 中的回調保存起來用於狀態改變時使用
 */

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function myPromise(fn){
  const that = this;
  that.value = null;
  that.status = PENDING; //默認狀態
  that.fulfilledCallbacks = [];
  that.rejectedCallbacks = [];
  function resolve(value){
    if(that.status === PENDING ){
      }
  }
 function reject(value){
    if(that.status === PENDING ){
      }
  }
    
  // 執行回調函數
   try{
        fn(resolve, reject)
    }catch (e) {
        reject(e);
    }
}
複製代碼

因而思考當在resolve裏該幹些什麼?resolve即執行狀態,首先status狀態值得變吧,改爲fulfilled狀態,同時將傳入的value值保存起來,以便下面的then會用到,最後得執行回調裏面的方法實現回調調用。reject同理,因而按這個思路,就有了:異步

/**
 * 建立三變量記錄表示狀態
 * 用that保存this,避免後期閉包致使this的指向不對
 * value 變量用於保存 resolve 或者 reject 中傳入的值
 * resolvedCallbacks 和 rejectedCallbacks 用於保存 then 中的回調,
 * 由於當執行完 Promise 時狀態可能仍是等待中,這時候應該把 then 中的回調保存起來用於狀態改變時使用
 */

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function myPromise(fn){
  const that = this;
  that.value = null;
  that.status = PENDING; //默認狀態
  that.fulfilledCallbacks = [];
  that.rejectedCallbacks = [];
  function resolve(value){
    if(that.status === PENDING ){
        that.status = FULFILLED;
        that.value = value;
        //執行回調方法
        that.fulfilledCallbacks.forEach(myFn => myFn(that.value))
      }
  }
 function reject(value){
    if(that.status === PENDING ){
        that.status = REJECTED;
        that.value = value;
        //執行回調方法
        that.rejectedCallbacks.forEach(myFn => myFn(that.value))
      }
  }
    
  // 執行回調函數
   try{
        fn(resolve, reject)
    }catch (e) {
        reject(e);
    }
}
複製代碼

因而promise的構造函數就簡陋的完成了,接下來實現then不就大工完成了嗎?是否是有些小興奮~~~函數

promise中then的實現

考慮到全部的實例都要用到then方法,在then得放在promise的原型鏈上。當狀態是PENDING狀態時,該作什麼?不執行回調,那就將回調方法分別放入不一樣的棧內,等待調用。當狀態爲FULFILLED或者REJECTED時,則執行響應的方法便可。因而:post

myPromise.prototype.then = function (onFulfilled, onRejected){
    let self = this;
    //等待狀態,則添加回調函數到棧中
    if(self.status === PENDING){
        self.fulfilledCallbacks.push(()=>{
            onFulfilled(self.value);
        });
        self.rejectedCallbacks.push(()=>{
            onRejected(self.value);
        })
    }
    if(self.status === FULFILLED){
        onFulfilled(self.value);
    }

    if(self.status === REJECTED){
        onRejected(self.value)
    }
}
複製代碼

因而一個簡單的promise實現以下:優化

/**
 * 建立三變量記錄表示狀態
 * 用that保存this,避免後期閉包致使this的指向不對
 * value 變量用於保存 resolve 或者 reject 中傳入的值
 * resolvedCallbacks 和 rejectedCallbacks 用於保存 then 中的回調,
 * 由於當執行完 Promise 時狀態可能仍是等待中,這時候應該把 then 中的回調保存起來用於狀態改變時使用
 */

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function myPromise(fn){
  const that = this;
  that.value = null;
  that.status = PENDING; //默認狀態
  that.fulfilledCallbacks = [];
  that.rejectedCallbacks = [];
  function resolve(value){
    if(that.status === PENDING ){
        that.status = FULFILLED;
        that.value = value;
        //執行回調方法
        that.fulfilledCallbacks.forEach(myFn => myFn(that.value))
      }
  }
 function reject(value){
    if(that.status === PENDING ){
        that.status = REJECTED;
        that.value = value;
        //執行回調方法
        that.rejectedCallbacks.forEach(myFn => myFn(that.value))
      }
  }
    
  // 執行回調函數
   try{
        fn(resolve, reject)
    }catch (e) {
        reject(e);
    }
}
 myPromise.prototype.then = function (onFulfilled, onRejected){
    let self = this;
    //等待狀態,則添加回調函數到棧中
    if(self.status === PENDING){
        self.fulfilledCallbacks.push(()=>{
            onFulfilled(self.value);
        });
        self.rejectedCallbacks.push(()=>{
            onRejected(self.value);
        })
    }
    if(self.status === FULFILLED){
        onFulfilled(self.value);
    }

    if(self.status === REJECTED){
        onRejected(self.value)
    }
}

let p = new thePromise((resolve, reject)=>{
    console.log('hello');
    resolve(5);
});
p.then((res)=>{
    console.log(res);
})
p.then(()=>{
    console.log('jj');
})
複製代碼

結果以下: ui

image.png
因而一個簡單的promise大工告成!

但是有沒有發現then裏並無返回一個promise,並不符合規範,因此能夠對promise進行部分優化。this

改造promise

根據promise+規範改造promise,傳給then的應該是個promise,以下: resolve 和reject的改造:

  • 對於 resolve 函數來講,首先須要判斷傳入的值是否爲 Promise 類型
  • 爲了保證函數執行順序,須要將兩個函數體代碼使用 setTimeout 包裹起來
function resolve(value) {
        if(value instanceof myPromise){
            return value.then(resolve, reject);
        }
        setTimeout(()=>{
            that.status = FULFILLED;
            that.value = value;
            //執行會回調方法
            that.fulfilledCallbacks.forEach(myFn => myFn(that.value))
        },0)
    }

    function reject(value) {
        setTimeout(()=>{
            that.status = REJECTED;
            that.value = value;
            //執行會回調方法
            that.rejectedCallbacks.forEach(myFn => myFn(that.value))
        }, 0);
    }
複製代碼

接下來改造then方法: 首先咱們須要新增一個變量 promise2,由於每一個 then 函數都須要返回一個新的 Promise 對象,該變量用於保存新的返回對象,因而:

myPromise.prototype.then = function (onFulfilled, onRejected) {
    let self = this;
    let promise2 = null;
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
    onRejected = typeof onRejected === 'function' ? onRejected : r => {throw r}
    //等待狀態,則添加回調函數到棧中
    if(self.status === PENDING){
        return (promise2 = new myPromise((resolve, reject)=>{
            self.fulfilledCallbacks.push(()=>{
                try {
                    let x = onFulfilled(self.value);
                    resolutionProduce(promise2, x, resolve, reject);
                }catch (e) {
                    reject(e)
                }
            });
            self.rejectedCallbacks.push(()=>{
                try {
                    let x = onRejected(self.value);
                    resolutionProduce(promise2, x, resolve, reject);
                }catch (e) {
                    reject(e);
                }
                onRejected(self.value);
            });
        }));
    }
    if(self.status === FULFILLED){
        return(promise2 = new myPromise((resolve, reject)=>{
            setTimeout(()=>{
                try {
                    let x = onFulfilled(self.value);
                    resolutionProduce(promise2, x, resolve, reject);
                }catch (e) {
                    reject(e);
                }
            }, 0)
        }));
    }

    if(self.status === REJECTED){
        return (promise2 = new myPromise((resolve, reject)=>{
            setTimeout(()=>{
                try {
                    let x = onRejected(self.value);
                    resolutionProduce(promise2, x, resolve, reject)
                }catch (e) {
                    reject(e);
                }
            },0)
        }));
    }
}
複製代碼

思路跟以前基本一致,只是說把以前返回值改爲promise,同時捕獲異常,在status狀態爲FULFILLED或者REJECTED的時候執行得加上異步setTimeout包裹。 接下來完成最核心的resolutionProduce函數:

  • 首先規範規定得保證當前的x不能與promise2一致,不然將執行無心義的相同操做,致使循環引用的發生。 例如:
let p = new myPromise((resolve, reject) => {
  resolve(1)
})
let p1 = p.then(value => {
  return p1
})
複製代碼
  • 而後須要判斷 x 的類型
if (x instanceof MyPromise) {
    x.then(function(value) {
        resolutionProcedure(promise2, value, resolve, reject)
    }, reject)
}
複製代碼

這裏的代碼是徹底按照規範實現的。若是 x 爲 Promise 的話,須要判斷如下幾個狀況:

  1. 若是 x 處於等待態,Promise 需保持爲等待態直至 x 被執行或拒絕
  2. 若是 x 處於其餘狀態,則用相同的值處理 Promise
function resolutionProduce(promise, x, resolve, reject){
 if(promise === x){
        return reject(new TypeError('Error'));
 }
 let called = false;
 if(x !== null && (typeof x === 'object' || typeof x === 'function')){
        try {
            let then = x.then;
            if(typeof then === 'function'){
                then.call(x, y=>{
                    if(called) return;
                    called = true;
                    resolutionProduce(promise2, y, resolve, reject )
                }, e =>{
                    if(e) return;
                    called = true;
                    reject(e);
                })
            }else {
                resolve(x);
            }
        }catch (e) {
            if(called) return;
            called = true;
            reject(e);
        }
    }else {
        resolve(x);
    }
}
複製代碼
  • 首先建立一個變量 called 用於判斷是否已經調用過函數
  • 而後判斷 x 是否爲對象或者函數,若是都不是的話,將 x 傳入 resolve 中
  • 若是 x 是對象或者函數的話,先把 x.then 賦值給 then,而後判斷 then 的類型,若是不是函數類型的話,就將 x 傳入 resolve 中
  • 若是 then 是函數類型的話,就將 x 做爲函數的做用域 this 調用之,而且傳遞兩個回調函數做爲參數,第一個參數叫作 resolvePromise ,第二個參數叫作 rejectPromise,兩個回調函數都須要判斷是否已經執行過函數,而後進行相應的邏輯
  • 以上代碼在執行的過程當中若是拋錯了,將錯誤傳入 reject 函數中

以上即是promise的簡單實現,下回有時間將把catch與all、race等方法實現,敬請期待 未完待續。。。 參考: juejin.im/post/5b88e0…

相關文章
相關標籤/搜索