手把手教你Promise的具體實現

一. Promise 與 Promise/A+
Promise是JS異步編程中的重要概念,異步抽象處理對象,是目前比較流行Javascript異步編程解決方案之一。
Promise/A+ 是 Promise 最小的一個規範。包括
- Promise 狀態
- then 方法
- Promise 解析過層
只有一個then 方法, 沒有catchraceall等方法
ECMAscript 6 Promise 是符合Promise/A+ 標準之一。

二. 具體實現

  1. PromiseA 大體框架
    舉個栗子:
let p = new Promise(function(resolve, reject) {
  resolve('200');
})

p.then(data => {
  console.log(data);
}, err => {
  console.log('err', err)
})

// 200

分析一下:
1. new Promise 返回一個promise對象, 接受一個executor執行器函數, 立即調用函數。
2. executor 接收兩個參數resolvereject, 同時這兩個參數也是函數。
3. Promise 實例具有狀態, 默認pending(等待), 執行器調用resolve後,實例狀態變爲resolved(成功)。 執行器調用reject,實例狀態變爲rejected(失敗)。
4. Promise 實例狀態一經改變, 將不能再修改。
5. promise 實例 都有 then方法。then 方法中有兩個參數。onResolved成功回調函數, onRejected失敗回調函數
6. 執行器executor調用resolve後, then中onResolved將會執行, 當執行器executor調用reject後, then方法第二個參數onRejected將會執行。

實現一下:

// promise 三個狀態
var PENDING = 'pending';
var RESOLVED = 'resolved';
var REJECTED = 'rejected';
function PromiseA (executor) {
  // 保存一下this, 防止this出現指向不明
  var _this = this; 
  // 初始化 promise 的值
  _this.data = undefined;
  // 初始化 promise 的狀態
  _this.status = PENDING;
  function resolve(value) {
    // 在pending時修改對應狀態, 和 promise 值
    if(_this.status === PENDING) {
      _this.status = RESOLVED;
      _this.data = value;
    }
  }
  function reject(reason) {
    // 在pending時修改對應狀態, 和 promise 值
    if(_this.status === PENDING) {
      _this.status = REJECTED;
      _this.data = reason;
    }
  }
  executor(resolve, reject);
}

PromiseA.prototype.then = function(onResolved, onRejected) {
  var _this = this;
  // 狀態是成功狀態, 立即執行成功回調, 並傳入其值
  if(_this.status === RESOLVED) {
    onResolved(_this.data);
  }
  // 狀態是失敗狀態, 立即執行失敗回調, 並傳入其值
  if(_this.status === REJECTED) {
    onRejected(_this.data);
  }
}

module.exports = PromiseA;
  1. 異步執行, then方法多次調用
    舉個栗子:
let p = new PromiseTest(function(resolve, reject) {
  setTimeout(() => {
    resolve('200');
  }, 1000)
})

p.then(data => {
  console.log(1, data)
}, err => {
  console.log('err', err)
})

p.then(data => {
  console.log(2, data)
}, err => {
  console.log('err', err)
})
// 1, '200'
// 2, '200'

分析一下:
結果將會在一秒中之後打印, 即then方法的失敗和成功回調是在promise 的異步執行完之後才觸發的,
所以 在調用then 方法的時候 promise 的狀態並不是成功或者失敗,
先將成功或者失敗的回調函數保存起來,等異步執行完成後再執行對應的成功或者失敗回調函數。
then 方法可以調用多次, 所以保存時需要使用數組

實現一下:

function PromiseA (executor) {
  // ...

  // 保存成功和失敗的回調函數
  _this.resolvedCallbacks = [];
  _this.rejectedCallbacks = [];

  // 調用成功函數
  function resolve(value) {
    // 在pending時修改對應狀態, 和 promise 值
    if(_this.status === PENDING) {
      _this.status = RESOLVED;
      _this.data = value;
      _this.resolvedCallbacks.forEach(function(fn) {
        fn();
      });
    }
  }
  // 調用失敗函數
  function reject(reason) {
    // 在pending時修改對應狀態, 和 promise 值
    if(_this.status === PENDING) {
      _this.status = REJECTED;
      _this.data = reason;
      _this.rejectedCallbacks.forEach(function(fn) {
        fn();
      });
    }
  }
}

PromiseA.prototype.then = function(onResolved, onRejected) {
  // ...

  // 狀態是等待, 將回調函數保存起來
  if(_this.status === PENDING) {
    _this.resolvedCallbacks.push(function() {
      onResolved(_this.data);
    })
    _this.rejectedCallbacks.push(function() {
      onRejected(_this.data);
    })
  }
}
  1. 錯誤捕獲
    舉個栗子:
let p = new PromiseA((resolve, reject) => {throw new Error('error')});
p.then(data => {
  console.log(data);
}, err => {
  console.log('err', err)
})
// err Error: error

分析一下:
Promise 出錯時會直接改變到失敗狀態, 並將失敗原因傳遞過去。
直接對執行函數executor 進行異常處理, 出錯就進入reject函數。

實現一下:

function PromiseA (executor) {
  // ...
  try {
    executor(resolve, reject);
  } catch (reason) {
    reject(reason);
  }
}
  1. then方法鏈式調用
    舉個栗子:
let p = new Promise(function(resolve, reject) {
  resolve('200');
})

p.then(data => {
  console.log(1, data)
  throw Error('oooo')
}, err => {
  console.log('1err', err)
}).then(data => {
  console.log(2, data);
}, err => {
  console.log('2err', err)
}).then(data => {
  console.log(3, data)
}, err => {
  console.log('3err', err)
})
console.log('start');

// start
// 1 '200'
// 2err Error: oooo
// 3 undefined

分析一下:

  1. promise 是異步執行函數。 故先打印start, 使用setTimeout 保證執行順序。
  2. Promise 實例調用then 方法後, 返回了一個新的Promise實例,
  3. 該Promise 執行成功或者失敗的結果, 傳遞給下一個promise實例的then方法 onResolvedonRejected 回調的參數。
  4. Promise 實例鏈式調用 then 時, 當任何一個then執行出錯時, 鏈時調用的下一個then時會執行錯誤的回調,
  5. 返回值未定義即undefined, 再次調用會執行成功的回調, 即上面的 3 undefined

實現一下:


function PromiseA (executor) {
  // ...
  function resolve(value) {
  // 在pending時修改對應狀態, 和 promise 值
    setTimeout(function() {
      if(_this.status === PENDING) {
        _this.status = RESOLVED;
        _this.data = value;
        _this.resolvedCallbacks.forEach(function(fn) {
          fn();
        });
      }
    })
  }

  function reject(reason) {
  // 在pending時修改對應狀態, 和 promise 值
    setTimeout(function() {
      if(_this.status === PENDING) {
        _this.status = REJECTED;
        _this.data = reason;
        _this.rejectedCallbacks.forEach(function(fn) {
          fn();
        });
      }
    })
  }
}

PromiseA.prototype.then = function(onResolved, onRejected) {
  var _this = this;
  var promise2;
  promise2 = new PromiseA(function(resolve, reject) {
    // 異步執行, 保證調用順序
    setTimeout(function() {
      // 狀態是成功狀態, 立即執行成功回調, 並傳入其值
      if(_this.status === RESOLVED) {
        // then方法執行 異常處理, 錯誤進入執行reject
        try {
          var x = onResolved(_this.data);
          resolvePromise(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason)
        }
      }
      // 狀態是失敗狀態, 立即執行失敗回調, 並傳入其值
      if(_this.status === REJECTED) {
        var x = onRejected(_this.data);
        resolvePromise(promise2, x, resolve, reject);
      }
    
      // 狀態是等待, 將回調函數保存起來
      if(_this.status === PENDING) {
        _this.resolvedCallbacks.push(function() {
          var x = onResolved(_this.data);
          resolvePromise(promise2, x, resolve, reject); 
        })
        _this.rejectedCallbacks.push(function() {
          var x = onRejected(_this.data);
          resolvePromise(promise2, x, resolve, reject);
        })
      }
    })
  })
  return promise2;
}

function resolvePromise(promise2, x, resolve, reject) {
  resolve(x);
}
  1. 值的穿透
    舉個栗子:
let p = new Promise(function(resolve, reject) {
  resolve('200');
})

p.then()
.then(null, err => {
  console.log('1err', err)
})
.then(data => {
  console.log(data)
}, err => {
  console.log('2err', err)
})
// '200'

分析一下:
當上一個then沒有傳遞迴調參數, 或者參數爲null時, 需要將值傳遞給下一個then方法
then方法的兩個參數都是可選參數onResolvedonRejected,
故, 判斷回調函數是否爲函數, 就把then的參數留空並且讓值穿透到後面。

實現一下:

PromiseA.prototype.then = function(onResolved, onRejected) {
  onResolved = typeof onResolved === 'function' ? onResolved : function(value) {return value};
  onRejected = typeof onRejected === 'function' ? onREjected : function(reason) {throw reason};
  
  // ...
}
  1. 循環引用
    舉個栗子:
let p = new Promise(function(resolve, reject) {
  resolve('200');
})

var p2 = p.then(() => {
  return p2;
})

p2.then(() => {
  console.log(1)
}, err => {
  console.log('err1', err)
})
// err1 TypeError: Chaining cycle detected for promise

分析一下:
上述代碼, 讓 p 的then方法回調自己, 就會產生循環回調,
故, then 方法中的回調函數不能是自己本身

實現一下:

function resolvePromise(promise2, x, resolve, reject) {
  if(promise2 === x) {
    return reject(new TypeError('循環引用'));
  }
}
// ...
  1. resolvePromise 函數實現
    resolvePromise 涉及到的Promise/A+ 規範
    • 將每個 Promise 實例調用then後返回的新 Promise 實例稱爲 promise2,將 then 回調返回的值稱爲x
    • promise2 不可以和 x 爲同一個對象, 自己等待自己, 循環引用。
    • 如果x是一個對象或者函數且不是null,就去取 x 的 then 方法,如果 x 是對象,防止x 是通過 Object.defineProperty 添加 then 屬性,並添加 get 和 set 監聽,如果在監聽中拋出異常,需要被捕獲到,x.then 是一個函數,就當作 x 是一個 Promise 實例,直接執行xthen 方法,執行成功就讓 promise2 成功,執行失敗就讓promise2 失敗,如果 x.then 不是函數,則說明 x 爲普通值,直接調用promise2resolve 方法將 x 傳入,不滿足條件說明該返回值就是一個普通值,直接執行 promise2resolve 並將 x 作爲參數傳入;
    • 如果每次執行xthen 方法,回調中傳入的參數還是一個 Promise 實例,循環往復,需要遞歸 resolvePromise 進行解析
    • 在遞歸的過程中存在內、外層同時調用了 resolvereject 的情況,應該聲明一個標識變量 called 做判斷來避免這種情況

實現一下:

function resolvePromise(promise2, x, resolve, reject) {
  var then;
  // 爲了避免多次調用
  var called = false;

  if(promise2 === x) {
    return reject(new TypeError('循環回調'));
  }

  // x 如果是普通值(非 object 或 function), 直接resolve
  if(x !== null && (typeof x === 'object' || typeof x === 'function')){ 
    // 每個promise 都會有then方法, 使用_then保存, 防止出錯, 使用try catch 
    try {
      then = x.then;
      if(typeof then === 'function') {
        // 確定 this 指向x
        then.call(x, function(y) {
          if(called) return;
          called = true;
          return resolvePromise(promise2, y, resolve, reject);
        }, function(e) {
          if(called) return;
          called = true;
          return reject(e);
        })

      } else {
        resolve(x);
      } 

    } catch (err) {
      if(called) return;
      called = true;
      reject(err);
    }

  } else {
    resolve(x);
  } 
}

三. 測試一下
使用 這個包promises-aplus-tests 走下單元測試
按其說明,需要提供一個這樣的類靜態函數:

PromiseA.deferred = PromiseA.defer = function() {
  var dfd = {}
  dfd.promise = new PromiseA(function(resolve, reject) {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}

四. 擴展方法

  1. catch方法
    舉個栗子:
let p = new Promise(function(resolve, reject) {
  resolve('200');
})

p.then(data => {
    throw new Error('eeee');
}, err => {
    console.log('err', err)
}).catch(err => {
    console.log(err)
})
// Error eeee

實現一下:

PromiseA.prototype.catch = function(onRejected) {
  return this.then(null, onRejected);
}
  1. resolve 方法
    舉個栗子:
let p = Promise.resolve('200');
p.then(data => {
    console.log(data)
}, err => {
    console.log('err', err)
})
// 200

實現一下:

Promise.prototype.resolve = function(value) {
  return new Promise(function(resolve, reject) {
    resolve(value);
  })
}
  1. reject 方法
    舉個栗子:
let p = Promise.reject('eeee');

p.then(data => {
    console.log(data)
}, err => {
    console.log('err', err)
})
// err eeee

實現一下:

Promise.reject = function(reason) {
  return new Promise(function(resolve, reject) {
    reject(reason)
  })
}
  1. race 方法
    舉個栗子:
let p1 = new Promise(function(resolve, reject) {
  setTimeout(() => {
    resolve(1)
  }, 1000)
}) 
let p2 = new Promise(function(resolve, reject) {
  setTimeout(() => {
    resolve(2)
  }, 2000)
}) 
let p3 = new Promise(function(resolve, reject) {
  setTimeout(() => {
    resolve(3)
  }, 3000)
}) 

let p = Promise.race([p1, p2, p3])
p.then(data => {
  console.log(data)
}, err => {
  console.log('err', err)
})

// 1

實現一下:

Promise.race = function(promises) {
  return new Promise(function(resolve, reject) {
    promises.forEach(function(promise) {
      promise.then(resolve, reject)
    }) 
  })
}

五. 全部代碼



// promise 三個狀態
var PENDING = 'pending';
var RESOLVED = 'resolved';
var REJECTED = 'rejected';

/**
 * 
 * @param {function} executor 
 */
function PromiseA (executor) {
  // 保存一下this, 防止this出現指向不明
  var _this = this; 

  // 初始化 promise 的值
  _this.data = undefined;

  // 初始化 promise 的狀態
  _this.status = PENDING;

  // 保存成功和失敗的回調函數
  _this.resolvedCallbacks = [];
  _this.rejectedCallbacks = [];

  // 調用成功函數
  function resolve(value) {
    // 在pending時修改對應狀態, 和 promise 值
    setTimeout(function() {
      if(_this.status === PENDING) {
        _this.status = RESOLVED;
        _this.data = value;
        _this.resolvedCallbacks.forEach(function(fn) {
          fn();
        });
      }
    })
  }

  // 調用失敗函數
  function reject(reason) {
    // 在pending時修改對應狀態, 和 promise 值
    setTimeout(function() {
      if(_this.status === PENDING) {
        _this.status = REJECTED;
        _this.data = reason;
        _this.rejectedCallbacks.forEach(function(fn) {
          fn();
        });
      }
    })
  }

  // 用於處理 new PromiseA((resolve, reject) => {throw new Error('error')})
  try {
    executor(resolve, reject);
  } catch (reason) {
    reject(reason)
  }
}

/**
 * 
 * @param {promise} promise2 then 執行後返回 新的Promise對象
 * @param {*} x promise中onResolved 的返回值
 * @param {*} resolve promise2的resolve方法
 * @param {*} reject promise2的reject方法
 */
function resolvePromise(promise2, x, resolve, reject) {
  var then;
  // 爲了避免多次調用
  var called = false;

  if(promise2 === x) {
    return reject(new TypeError('循環回調'));
  }

  // x 如果是普通值(非 object 或 function), 直接resolve
  if(x !== null && (typeof x === 'object' || typeof x === 'function')){ 
    // 每個promise 都會有then方法, 使用_then保存, 防止出錯, 使用try catch 
    try {
      then = x.then;
      if(typeof then === 'function') {
        // 確定 this 指向x
        then.call(x, function(y) {
          if(called) return;
          called = true;
          return resolvePromise(promise2, y, resolve, reject);
        }, function(e) {
          if(called) return;
          called = true;
          return reject(e);
        })

      } else {
        resolve(x);
      } 

    } catch (err) {
      if(called) return;
      called = true;
      reject(err);
    }

  } else {
    resolve(x);
  } 
}

/**
 * @param {function} onResolved 成功回調
 * @param {function} onRejected 失敗回調
 */
PromiseA.prototype.then = function(onResolved, onRejected) {
  var _this = this;
  var promise2;
  // 值的穿透
  onResolved = typeof onResolved === 'function' ? onResolved : function(value) {return value};
  onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {throw reason};

  return promise2 = new PromiseA(function(resolve, reject) {
    // 異步執行, 保證調用順序
    setTimeout(function() {
      // 狀態是成功狀態, 立即執行成功回調, 並傳入其值
      if(_this.status === RESOLVED) {
        // 針對內部
        try {
          var x = onResolved(_this.data);
          resolvePromise(promise2, x, resolve, reject);
        } catch(reason) {
          reject(reason);
        }
      }
      // 狀態是失敗狀態, 立即執行失敗回調, 並傳入其值
      if(_this.status === REJECTED) {
        try {
          var x = onRejected(_this.data);
          resolvePromise(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      }
    
      // 狀態是等待, 將回調函數保存起來
      if(_this.status === PENDING) {
        _this.resolvedCallbacks.push(function() {
          try {
            var x = onResolved(_this.data);
            resolvePromise(promise2, x, resolve, reject); 
          } catch (reason) {
            reject(reason);
          }
        })
        _this.rejectedCallbacks.push(function() {
          try {
            var x = onRejected(_this.data);
            resolvePromise(promise2, x, resolve, reject);
          } catch (reason) {
            reject(reason)
          }
        })
      }
    })
  })
}

PromiseA.prototype.catch = function(onRejected) {
  return this.then(null, onRejected);
}

PromiseA.resolve = function(value) {
  return new PromiseA(function(resolve, reject) {
    resolve(value);
  })
}

PromiseA.reject = function(reason) {
  return new PromiseA(function(resolve, reject) {
    reject(reason)
  })
}

PromiseA.race = function(promises) {
  return new PromiseA(function(resolve, reject) {
    promises.forEach(function(promise) {
      promise.then(resolve, reject)
    }) 
  })
}

//  配合使用 promises-aplus-tests 測試
PromiseA.deferred = PromiseA.defer = function() {
  var dfd = {}
  dfd.promise = new PromiseA(function(resolve, reject) {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}

module.exports = PromiseA;

六. Reference

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章