Promise/A+ 實現

一. Promise 與 Promise/A+

Promise是JS異步編程中的重要概念,異步抽象處理對象,是目前比較流行Javascript異步編程解決方案之一。git

Promise/A+ 是 Promise 最小的一個規範。包括es6

- Promise 狀態
- then 方法
- Promise 解析過層 
複製代碼

只有一個then 方法, 沒有catchraceall等方法 ECMAscript 6 Promise 是符合Promise/A+ 標準之一。github

二. 具體實現

  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` 接收兩個參數`resolve`、`reject`, 同時這兩個參數也是函數。
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'
複製代碼

分析一下:promise

結果將會在一秒中以後打印, 即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

相關文章
相關標籤/搜索