手把手教你實現Promise(一)(基於Promise A+規範)

使用Promise能解決回調地獄多個異步請求等問題。那麼它是怎麼實現的呢?讓咱們一塊兒來實現一下吧jquery

同步調用的實現

首先,咱們要知道:npm

  • Promise是一個類
  • new Promise 時,會返回一個promise的對象,它會傳一個執行器(executor),這個執行器是當即執行的
  • 另外每一個promise實例上都會有一個then方法,參數分別是成功(有成功的值)和失敗(有失敗的原用)兩個方法
  • promise有三個狀態:成功態,失敗態,等待態。
  • 默認狀態是等待態,等待態能夠變成成功態或失敗態
  • 一旦成功或失敗就不能再變會其餘狀態了 知道這些就能夠先簡單實現一下了
class Promise {
  constructor(executor) { //executor執行器
    this.status = 'pending'; //默認等待狀態
    this.value = undefined; //成功的值
    this.reason = undefined //失敗的原用

    let resolve = (value) => {
      if (this.status === 'pending') {
        this.status = 'resolved'; //成功
        this.value = value;
      }
    }
    let reject = (reason) => {
      if (this.status === 'pending') {
        this.status = 'rejected'; //失敗
        this.reason = reason;
      }
    }
    executor(resolve, reject); //默認上執行器執行
  }
  then(onFufilled, onRejected) {
    if (this.status === 'resolved') { //成功態
      onFufilled(this.value);
    }
    if (this.status === 'rejected') { //失敗態
      onRejected(this.reason);
    }
  }
}
module.exports = Promise
複製代碼

以上,咱們就簡單的實現了一個同步的promise。 測試一下吧數組

let Promise = require('./myPromise.js')
let promise = new Promise((resolve, reject) => {
    resolve('hello')
})
promise.then((data) => {
    console.log(data)
}, (err) => {
    console.log(err)
})
複製代碼

打印結果:promise

可是,咱們知道,promise主要解決的是異步回調問題。因此,異步調用必須實現起來。bash

異步調用的實現

當異步調用時,當調用實例的then時,狀態可能還處於pending狀態,這時咱們須要在實例上定義兩個存放成功和失敗方法的數組,把須要執行的方法分別放到對應的數組裏,等到異步時間到達的時候,再去執行對應數組裏的方法。異步

class Promise {
  constructor(executor) { //executor執行器
    this.status = 'pending'; //默認等待狀態
    this.value = undefined; //成功的值
    this.reason = undefined //失敗的原用
   + //存放then成功,失敗的回調的數組
    this.onResovleCallbacks = [];
    this.onRejectedCallbacks = [];
    
    let resolve = (value) => {
      if (this.status === 'pending') {
        this.status = 'resolved'; //成功
        this.value = value;
    +    this.onResovleCallbacks.forEach(fn => fn());
      }
    }
    let reject = (reason) => {
      if (this.status === 'pending') {
        this.status = 'rejected'; //失敗
        this.reason = reason;
    +    this.onRejectedCallbacks.forEach(fn => fn());
      }
    }
    executor(resolve, reject); //默認上執行器執行
  }
  then(onFufilled, onRejected) {
    if (this.status === 'resolved') { //成功態
      onFufilled(this.value);
    }
    if (this.status === 'rejected') { //失敗態
      onRejected(this.reason);
    }
+    if (this.status === 'pending') {
      this.onResovleCallbacks.push(() => {
        onFufilled(this.value)
      });
      this.onRejectedCallbacks.push(() => {
        onRejected(this.reason)
      })
    }
  }
}

module.exports = Promise
複製代碼

以上,咱們就實現了promise的異步調用。 測試一下吧函數

let Promise = require('./myPromise.js')
let promise = new Promise((resolve, reject) => {
    setTimeout(function(){
        resolve('hello')
    },100)
})
promise.then((data) => {
    console.log(data)
}, (err) => {
    console.log(err)
})
複製代碼

打印結果: 測試

非正常執行處理

當執行的時候拋出異常時,咱們應該讓它當狀態變爲rejected,去執行then的錯誤方法。 這時候,須要在執行器執行的時候 捕獲一下錯誤,並做出rejected處理ui

try {
      executor(resolve, reject);
    } catch (e) { //捕獲到異常時,直接走失敗
      reject(e);
    }
複製代碼

測試一下吧this

let Promise = require('./myPromise.js')
let promise = new Promise((resolve, reject) => {
   throw new Error('❌')
})
promise.then((data) => {
    console.log(data)
}, (err) => {
    console.log(err)
})
複製代碼

打印結果:

鏈式調用的實現

說到鏈式調用,我們接觸最多的就是jquery,jquery實現鏈式調用是靠的是返回this,promise實現鏈式調用是否是也返回this呢?答案是,NO !它實現鏈式調用靠的是返回一個新的promise。 在then方法裏,不管promise處於哪一種狀態,執行完後,都返回一個新的promise。

then(onFufilled, onRejected) {
  +  let promise2; //返回的新promise
  +  promise2 = new Promise((resolve, reject) => {
      if (this.status === 'resolved') {
        onFufilled(this.value);
      }
      if (this.status === 'rejected') { 
        onRejected(this.reason);
      }
      if (this.status === 'pending') {
        this.onResovleCallbacks.push(() => {
          onFufilled(this.value)
        });
        this.onRejectedCallbacks.push(() => {
          onRejected(this.reason)
        })
      }
    });
  +  return promise2;
  }
複製代碼

鏈式調用之錯誤狀況

在then中,不管是成功的回調仍是失敗的回調,只要返回告終果就會走下一個then中的成功,若是有錯誤,就會走下一個then的失敗回調。即:下一個then的狀態跟上一個then執行時候的狀態無關。 因此,在then執行的時候,onFufilled, onRejected可能會出錯,這時候,咱們須要捕獲錯誤,並處理成失敗

promise2 = new Promise((resolve, reject) => {
      if (this.status === 'resolved') {
        try {
          onFufilled(this.value);
        } catch (e) {
          reject(e)
        }
      }
      if (this.status === 'rejected') {
        try {
          onRejected(this.reason);
        } catch (e) {
          reject(e)
        }
      }
      if (this.status === 'pending') {
        this.onResovleCallbacks.push(() => {
          try {
            onFufilled(this.value)
          } catch (e) {
            reject(e);
          }
        });
        this.onRejectedCallbacks.push(() => {
          try {
            onRejected(this.reason)
          } catch (e) {
            reject(e);
          }
        })
      }
    });
複製代碼

測試一下吧

let promise = new Promise((resolve, reject) => {
    resolve('hello')
})
promise.then((data) => {
    console.log(data)
    throw new Error('🙅')
}, (err) => {
    console.log(err)
}).then((data) => {
    console.log(data)
}, (err) => {
    console.log('🙅' + err)
})
複製代碼

打印結果:

鏈式調用之兼容多種狀況

  • 若是第一個promise返回一個普通值,直接將這個返回值,傳遞給下一次then的resolve。
  • 若是第一個promise返回一個promise,須要等待返回的這個promise執行後的結果,傳給下一次then 處理第一次promise執行後的返回值x,then方法的每一個狀態都須要處理一下:
try {
		  //x是上一個promise返回值,多是一個普通值,也多是一個promise;x也多是別人的promise,咱們能夠寫一個方法,統一處理
          let x=onFufilled(this.value);
          //入參:下一次then的實例promise2,此次返回值x,promise2的成功方法,promise2的失敗方法
          resolvePromise(promise2,x,resolve,reject);
        } catch (e) {
          reject(e)
        }
複製代碼

下面來實現resolvePromise,用來處理多套promise共用的狀況:

/*
*	resolvePromise 
*	@Parameters
*		promise2:	下一次then的實例promise2
*		x:			此次返回值x
*		resolve:	promise2的成功方法
*		reject:	promise2的失敗方法
*/
function resolvePromise(promise2, x, resolve, reject) {
      //x多是別人的promise,因此儘量的容許別人瞎寫
      if (promise2 === x) { //返回的結果和promise是同一個,那麼永遠不會成功
        return reject(new TypeError('循環引用'));
      }
      let called;//是否調用過成功或失敗
      // 看x是否是promise。promise應該是一個對象
      if (x != null && (typeof x === 'object' || typeof x === 'function')) { //多是promise
        try {
          let then = x.then; // 若是是對象 就試着取一下then方法 若是有then,認爲它是promise
          if (typeof then === 'function') { // 若是then是函數,是promise
            then.call(x, y => {
              // 成功和失敗只能調用一個
              if (called) return;
              called = true;
              // resolve的結果依舊是promise 那就繼續解析
              resolvePromise(promise2, y, resolve, reject);
            }, r => {
              if (called) return;
              called = true;
              reject(r); // 失敗了就失敗了
            })
          } else {
            resolve(x); // 直接成功便可
          }
        } catch (e) { // 取then出錯了那就不要在繼續執行了
          if (called) return;
          called = true;
          reject(e);
        }
      } else { //普通值 讓promise2直接變成成功態
        resolve(x);
      }
    };
複製代碼

測試一下吧

  • 返回一個普通值
let promise = new Promise((resolve, reject) => {
    resolve('hello')
})
promise.then((data) => {
    console.log(data)
    throw new Error('🙅')
}, (err) => {
    console.log(err)
}).then((data) => {
    console.log(data)
}, (err) => {
    console.log('🙅' + err)
})
複製代碼

打印結果:

  • 返回一個promise
let promise = new Promise((resolve, reject) => {
    resolve('hello')
})
promise.then((data) => {
    console.log(data)
    return new Promise((resolve, reject) => {
        resolve('👋')
    })
}, (err) => {
    console.log(err)
}).then((data) => {
    console.log(data)
}, (err) => {
    console.log('🙅' + err)
})
複製代碼

打印結果:

以上,咱們的promise好像已經差很少了,可是還有一個問題,須要處理。源碼能夠在hen中實現什麼都不傳。promise中管這種現象叫,值的穿透。 所以,咱們須要在then方法裏,對then方法的入參進行容錯處理:

onFufilled = typeof onFufilled === 'function' ? onFufilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err};
複製代碼

測試一下吧

let Promise = require('./myPromise.js')
let promise = new Promise((resolve, reject) => {
    resolve('hello')
})
promise.then().then((data) => {
    console.log(data)
}, (err) => {
    console.log('🙅' + err)
})
複製代碼

打印結果:

另外,promise規範中要求,全部的onFufilledonRejected都須要異步執行,若是不加異步可能形成測試的不穩定性,因此咱們給執行這兩個方法執行的地方都加上異步方法。

if (this.status === 'resolved') {
        setTimeout(() => {
          try {
            let x=onFufilled(this.value);
            resolvePromise(promise2,x,resolve,reject);
          } catch (e) {
            reject(e)
          }
        }, 0);
      }
複製代碼

Promise實現

class Promise {
  constructor(executor) { //executor執行器
    this.status = 'pending'; //默認等待狀態
    this.value = undefined; //成功的值
    this.reason = undefined //失敗的原用
    this.onResovleCallbacks = [];
    this.onRejectedCallbacks = [];
    let resolve = (value) => {
      if (this.status === 'pending') {
        this.status = 'resolved'; //成功
        this.value = value;
        this.onResovleCallbacks.forEach(fn => fn());
      }
    }
    let reject = (reason) => {
      if (this.status === 'pending') {
        this.status = 'rejected'; //失敗
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    }
    try {
      executor(resolve, reject); //默認上執行器執行
    } catch (e) { //捕獲到異常時,直接走失敗
      reject(e);
    }
  }
  then(onFufilled, onRejected) {
    onFufilled = typeof onFufilled === 'function' ? onFufilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : err => {
      throw err
    };
    function resolvePromise(promise2, x, resolve, reject) {
      //x多是別人的promise,因此儘量的容許別人瞎寫
      if (promise2 === x) { //返回的結果和promise是同一個,那麼永遠不會成功
        return reject(new TypeError('循環引用'));
      }
      //
      let called;
      // 看x是否是promise。promise應該是一個對象
      if (x != null && (typeof x === 'object' || typeof x === 'function')) { //多是promise
        try {
          let then = x.then; // 若是是對象 我就試着取一下then方法 若是有then,認爲它是promise
          if (typeof then === 'function') { // then是函數,是promise
            then.call(x, y => {
              // 成功和失敗只能調用一個
              if (called) return;
              called = true;
              // resolve的結果依舊是promise 那就繼續解析
              resolvePromise(promise2, y, resolve, reject);
            }, r => {
              if (called) return;
              called = true;
              reject(r); // 失敗了就失敗了
            })
          } else {
            resolve(x); // 直接成功便可
          }
        } catch (e) { // 取then出錯了那就不要在繼續執行了
          if (called) return;
          called = true;
          reject(e);
        }
      } else { //普通值 讓promise2直接變成成功態
        resolve(x);
      }
    };
    let promise2; //返回的新promise
    promise2 = new Promise((resolve, reject) => {
      if (this.status === 'resolved') {
        setTimeout(() => {
          try {
            let x = onFufilled(this.value); //x是上一個promise返回值,多是一個普通值,也多是一個promise;x也多是別人的promise,咱們能夠寫一個方法,統一處理
            resolvePromise(promise2, x, resolve, reject); //下一次then的實例promise2,此次返回值x,promise2的成功方法,promise2的失敗方法
          } catch (e) {
            reject(e)
          }
        }, 0);
      }
      if (this.status === 'rejected') {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e)
          }
        }, 0)
      }
      if (this.status === 'pending') {
        this.onResovleCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFufilled(this.value)
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0)
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason)
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0)
        })
      }
    });
    return promise2;
  }
}
module.exports = Promise
複製代碼

測試

以上,咱們基本完成了一個本身的promise庫。 最後,看看這個庫可不可行,那麼就須要測試。官方給出了一個測試的庫promises-aplus-tests,它會幫咱們校驗,這個庫是否可行。另外測試須要用defer,它是promise的語法糖。

Promise.defer = Promise.deferred = function () {
  let dfd = {}
  dfd.promise = new Promise((resolve, reject) => {
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
}
複製代碼

安裝

npm install -g promises-aplus-tests
複製代碼

執行

promises-aplus-tests ./myPromise.js 
複製代碼

以上,咱們就本身完成了一個基於Promise A+規範的Promise。

相關文章
相關標籤/搜索