史上最最最詳細的手寫Promise教程

咱們工做中免不了運用promise用來解決異步回調問題。平時用的不少庫或者插件都運用了promise 例如axios、fetch等等。可是你知道promise是咋寫出來的呢?ios

別怕~這裏有本promisesA+規範,便宜點10元賣給你了。npm

 

一、Promise 的聲明

首先呢,promise確定是一個類,咱們就用class來聲明。axios

  • 因爲new Promise((resolve, reject)=>{}),因此傳入一個參數(函數),祕籍裏叫他executor,傳入就執行。
  • executor裏面有兩個參數,一個叫resolve(成功),一個叫reject(失敗)。
  • 因爲resolve和reject可執行,因此都是函數,咱們用let聲明。
class Promise{
  // 構造器
  constructor(executor){
    // 成功
    let resolve = () => { };
    // 失敗
    let reject = () => { };
    // 當即執行
    executor(resolve, reject);
  }
}

解決基本狀態

祕籍對Promise有規定:

  • Promise存在三個狀態(state)pending、fulfilled、rejected數組

  • pending(等待態)爲初始態,並能夠轉化爲fulfilled(成功態)和rejected(失敗態)promise

  • 成功時,不可轉爲其餘狀態,且必須有一個不可改變的值(value)異步

  • 失敗時,不可轉爲其餘狀態,且必須有一個不可改變的緣由(reason)函數

  • new Promise((resolve, reject)=>{resolve(value)}) resolve爲成功,接收參數value,狀態改變爲fulfilled,不可再次改變。post

  • new Promise((resolve, reject)=>{reject(reason)}) reject爲失敗,接收參數reason,狀態改變爲rejected,不可再次改變。測試

  • 如果executor函數報錯 直接執行reject();fetch

因而乎,咱們得到如下代碼

class Promise{
  constructor(executor){
    // 初始化state爲等待態
    this.state = 'pending';
    // 成功的值
    this.value = undefined;
    // 失敗的緣由
    this.reason = undefined;
    let resolve = value => {
      // state改變,resolve調用就會失敗
      if (this.state === 'pending') {
        // resolve調用後,state轉化爲成功態
        this.state = 'fulfilled';
        // 儲存成功的值
        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.value
  • onFulfilled,onRejected若是他們是函數,則必須分別在fulfilled,rejected後被調用,value或reason依次做爲他們的第一個參數
class Promise{
  constructor(executor){...}
  // then 方法 有兩個參數onFulfilled onRejected
  then(onFulfilled,onRejected) {
    // 狀態爲fulfilled,執行onFulfilled,傳入成功的值
    if (this.state === 'fulfilled') {
      onFulfilled(this.value);
    };
    // 狀態爲rejected,執行onRejected,傳入失敗的緣由
    if (this.state === 'rejected') {
      onRejected(this.reason);
    };
  }
}

這下武學初成,能夠對付對付江湖小雜毛了,可是對於帶setTimeout的江洋大盜仍是沒轍。

 

解決異步實現

如今基本能夠實現簡單的同步代碼,可是當resolve在setTomeout內執行,then時state仍是pending等待狀態 咱們就須要在then調用的時候,將成功和失敗存到各自的數組,一旦reject或者resolve,就調用它們

相似於發佈訂閱,先將then裏面的兩個函數儲存起來,因爲一個promise能夠有多個then,因此存在同一個數組內。

// 多個then的狀況
let p = new Promise();
p.then();
p.then();

成功或者失敗時,forEach調用它們

class Promise{
  constructor(executor){
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    // 成功存放的數組
    this.onResolvedCallbacks = [];
    // 失敗存放法數組
    this.onRejectedCallbacks = [];
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        // 一旦resolve執行,調用成功數組的函數
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    };
    let reject = reason => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        // 一旦reject執行,調用失敗數組的函數
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    };
    try{
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled,onRejected) {
    if (this.state === 'fulfilled') {
      onFulfilled(this.value);
    };
    if (this.state === 'rejected') {
      onRejected(this.reason);
    };
    // 當狀態state爲pending時
    if (this.state === 'pending') {
      // onFulfilled傳入到成功數組
      this.onResolvedCallbacks.push(()=>{
        onFulfilled(this.value);
      })
      // onRejected傳入到失敗數組
      this.onRejectedCallbacks.push(()=>{
        onRejected(this.value);
      })
    }
  }
}

解決鏈式調用

我門經常用到new Promise().then().then(),這就是鏈式調用,用來解決回調地獄

一、爲了達成鏈式,咱們默認在第一個then裏返回一個promise。祕籍規定了一種方法,就是在then裏面返回一個新的promise,稱爲promise2:promise2 = new Promise((resolve, reject)=>{})

  • 將這個promise2返回的值傳遞到下一個then中
  • 若是返回一個普通的值,則將普通的值傳遞給下一個then中

二、當咱們在第一個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 => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    };
    let reject = reason => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    };
    try{
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled,onRejected) {
    // 聲明返回的promise2
    let promise2 = new Promise((resolve, reject)=>{
      if (this.state === 'fulfilled') {
        let x = onFulfilled(this.value);
        // resolvePromise函數,處理本身return的promise和默認的promise2的關係
        resolvePromise(promise2, x, resolve, reject);
      };
      if (this.state === 'rejected') {
        let x = onRejected(this.reason);
        resolvePromise(promise2, x, resolve, reject);
      };
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(()=>{
          let x = onFulfilled(this.value);
          resolvePromise(promise2, x, resolve, reject);
        })
        this.onRejectedCallbacks.push(()=>{
          let x = onRejected(this.value);
          resolvePromise(promise2, x, resolve, reject);
        })
      }
    });
    // 返回promise,完成鏈式
    return promise2;
  }
}

完成resolvePromise函數

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

  • 若是 x === promise2,則是會形成循環引用,本身等待本身完成,則報「循環引用」錯誤
let p = new Promise(resolve => {
  resolve(0);
});
var p2 = p.then(data => {
  // 循環引用,本身等待本身完成,一生完不成
  return p2;
})

一、判斷x

  • Otherwise, if x is an object or function,Let then be x.then
  • x 不能是null
  • x 是普通值 直接resolve(x)
  • x 是對象或者函數(包括promise),let then = x.then 二、當x是對象或者函數(默認promise)
  • 聲明瞭then
  • 若是取then報錯,則走reject()
  • 若是then是個函數,則用call執行then,第一個參數是this,後面是成功的回調和失敗的回調
  • 若是成功的回調仍是pormise,就遞歸繼續解析 三、成功和失敗只能調用一個 因此設定一個called來防止屢次調用
function resolvePromise(promise2, x, resolve, reject){
  // 循環引用報錯
  if(x === promise2){
    // reject報錯
    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);
  }
}

解決其餘問題

一、祕籍規定onFulfilled,onRejected都是可選參數,若是他們不是函數,必須被忽略

  • onFulfilled返回一個普通的值,成功時直接等於 value => value
  • onRejected返回一個普通的值,失敗時若是直接等於 value => value,則會跑到下一個then中的onFulfilled中,因此直接扔出一個錯誤reason => throw err 二、祕籍規定onFulfilled或onRejected不能同步被調用,必須異步調用。咱們就用setTimeout解決異步問題
  • 若是onFulfilled或onRejected報錯,則直接返回reject()
class Promise{
  constructor(executor){
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    };
    let reject = reason => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    };
    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 };
    let promise2 = new Promise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        // 異步
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            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);
      };
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(() => {
          // 異步
          setTimeout(() => {
            try {
              let x = onFulfilled(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)
        });
      };
    });
    // 返回promise,完成鏈式
    return promise2;
  }
}

大功告成

 

順便附贈catch和resolve、reject、race、all方法

 

class Promise{
  constructor(executor){
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    };
    let reject = reason => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    };
    try{
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled,onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
    let promise2 = new Promise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            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);
      };
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(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;
  }
  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;
  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;
          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);
  }
}
//resolve方法
Promise.resolve = function(val){
  return new Promise((resolve,reject)=>{
    resolve(val)
  });
}
//reject方法
Promise.reject = function(val){
  return new Promise((resolve,reject)=>{
    reject(val)
  });
}
//race方法 
Promise.race = function(promises){
  return new Promise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(resolve,reject)
    };
  })
}
//all方法(獲取全部的promise,都執行then,把結果放到數組,一塊兒返回)
Promise.all = function(promises){
  let arr = [];
  let i = 0;
  function processData(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);
    };
  });
}

如何驗證咱們的promise是否正確

一、先在後面加上下述代碼

二、npm 有一個promises-aplus-tests插件 npm i promises-aplus-tests -g 能夠全局安裝 mac用戶最前面加上sudo

三、命令行 promises-aplus-tests [js文件名] 便可驗證

// 目前是經過他測試 他會測試一個對象
// 語法糖
Promise.defer = Promise.deferred = function () {
  let dfd = {}
  dfd.promise = new Promise((resolve,reject)=>{
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
}
module.exports = Promise;
//npm install promises-aplus-tests 用來測試本身的promise 符不符合promisesA+規範

做者:卡姆愛卡姆 連接:https://juejin.im/post/5b2f02cd5188252b937548ab 來源:掘金 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。

相關文章
相關標籤/搜索