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

lily醬發表於2018-06-11

使用Promise能解決回撥地獄多個非同步請求等問題。那麼它是怎麼實現的呢?讓我們一起來實現一下吧

同步呼叫的實現

首先,我們要知道:

  • 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 A+規範)

但是,我們知道,promise主要解決的是非同步回撥問題。所以,非同步呼叫必須實現起來。

非同步呼叫的實現

當非同步呼叫時,當呼叫例項的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)
})
複製程式碼

列印結果:

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

非正常執行處理

當執行的時候丟擲異常時,我們應該讓它當狀態變為rejected,去執行then的錯誤方法。 這時候,需要在執行器執行的時候 捕獲一下錯誤,並作出rejected處理

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

測試一下吧

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

列印結果:

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

鏈式呼叫的實現

說到鏈式呼叫,我們們接觸最多的就是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(一)(基於Promise A+規範)

鏈式呼叫之相容多種情況

  • 如果第一個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(一)(基於Promise A+規範)

  • 返回一個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(一)(基於Promise A+規範)

以上,我們的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(一)(基於Promise A+規範)

另外,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。

相關文章