今天要写一个符合 a+ 规范的Promise。
a+规范地址(保姆级的开发规范):https://promisesaplus.com/
Promise 是基于回调实现的,通过回调函数队列来管理异步代码。这点与发布-订阅模式很相似。
Promise A+
术语
Promise A+ 规范有几个专门的术语,先来介绍下,以便后面的介绍。
- promise 是一个对象或者函数,拥有一个then方法,这个方法行为符合 A+ 规范。
- thenable 是一个对象或者函数,这个对象或函数定义了一个 then 方法。
- value 是任何合法的 JavaScript 值,包括undefined,thenable 和 promise。
- exception 异常,是用throw抛出的错误。
- reason 原因,是一个值表明 promise 为什么被拒绝。
必备项
Promise的状态
promise 有三种状态:pending(初始态/等待态)、fulfilled(成功态/完成态)、rejected(失败态/拒绝态)。promise必定是这三个状态之一。有以下几个特点:
- 当 promise 是初始态时,状态可以转变为成功态或者失败态。
- 当 promise 是完成态时,状态不能改变为其他状态,并且必定有一个值 value(返回之后不可改变)。
当 promise 是失败态时,状态不能改变为其他状态,并且必定有一个原因 reason(返回之后不可改变)。
function Promise() {
this.status = 'pending'; // pending / fulfilled / rejected
}
then 方法
promise 必定会提供一个 then 方法获取它当前的或者最终的值或原因。then 方法接受两个函数作为参数 onFulfilled 和 onRejected,使用如下:
promise.then(onFulfilled, onRejected);
onFulfilled 和 onRejected 都是可选的。
- 如果 onFulfilled 不是一个函数,就要被忽略。
- 如果 onRejected 不是一个函数,就要被忽略。
- 如果 onFulfilled 是一个函数。
- 它一定是 promise 在成功之后被调用,并且以 promise 的值 value 作为第一个参数。
- 它不能在成功之前被调用。
- 它不能被调用多次。
- 如果 onRejected 是一个函数。
- 它一定是 promise 在失败之后被调用,并且以 promise 的原因 reason 作为第一个参数。
- 它不能在失败之前被调用。
- 它不能被调用多次。
- onFulfilled 和 onRejected 必须直到执行上下文栈仅包含平台代码才能调用。
- onFulfilled 和 onRejected 必须作为函数被调用。
- then 方法可以被调用多次,在同一个promise内。
- 当 promise 成功之后,所有的各自的 onFulfilled 回调函数都将按照他们起源的then方法的顺序被执行被执行。
- 当 promise 失败之后,所有的各自的 onRejected 回调函数都将按照他们起源的then方法的顺序被执行被执行。
then 方法会返回一个新的 promise。
promise2 = promise1.then(onFulfilled, onRejected);
- 无论是 onFulfilled 还是 onRejected 返回了一个值 x,都要运行 promise 解决方案。
- 无论是 onFulfilled 还是 onRejected 抛出了错误异常,promise2 必须被拒绝并且以 e 作为参数。
- 如果 onFulfilled 不是一个函数,并且 promise1 已经成功,promise2 必须成功并且返回相同的值。
- 如果 onRejected 不是一个函数,并且 promise1 已经失败,promise2 必须失败并且返回相同的原因。
The Promise Resolution Procedure
Promise 的解决程序是一个抽象操作,取得输入的 promise 和值 x(结果)。
如果 promise 和 x 引用的同一个对象,就抛出一个 TypeError。
- 如果 x 是个 promise,则采用它的状态。
- 如果 x 是等待态,promise 保持 pending 直到它被解决或者拒绝。
- 如果 x 被解决,采用相同的值 value。
- 如果 x 被拒绝,采用相同的原因 reason。
- 否则 x 是个对象或者方法
- let then = x.then;
- 如果取到的属性 x.then 导致抛出异常,拒绝 promise 并把抛出的错误 e 作为原因。
- 如果 then 是一个方法,将 then 的 this 绑定为 x,第一个参数为解决函数 resolvePromise,第二个参数为拒绝函数 rejectPromise。
- 当 resolvePromise 被调用时,会被传入值 y,并且递归调用 Promise 解决程序。
- 当 rejectPromise 被调用时,会被传入原因 r,然后以 r 为参数拒绝 promise2。
- 如果 resolvePromise 和 rejectPromise 都被调用,或者对同一个参数进行多次调用,则第一次调用优先,并且任何进一步的调用都会被拒绝。
- 如果 then 抛出异常 e
- 如果 resolvePromise 和 rejectPromise 已经被调用则忽略它
- 否则以 e 为原因拒绝 promise2
- 如果 then 不是一个函数,以 x 为参数解决promise
- 如果 x 不是一个对象或者函数,以 x 为参数解决promise
实现主干代码
构造函数
首先,我们要明确 Promise 是通过 new 声明的,所以必然是个构造函数,并且接受一个函数(executor)作为参数。executor 函数提供两个内部函数 resolve 和 reject 作为参数:
- resolve 当成功时调用,接收一个 value 作为成功的值
reject 当失败时调用,接受一个 reason 作为失败的原因
// Promise 构造函数
function Promise(executor) {
function resolve(value) { //变成成功态
//...
}
function reject(reason) { //变成失败态
//...
}
executor(resolve, reject);//立即执行
}
下面添加一些变量用于保存内部数据:
state 用于保存 promise的三个状态:pending、fulfilled、rejected。
- value 保存成功返回的值。
reason 保存失败返回的原因。
function Promise(executor) {
this.status = 'pending'; // pending / fulfilled / rejected
this.value = undefined;
this.reason = undefined;
function resolve(value) { //变成成功态
//...
}
function reject(reason) { //变成失败态
//...
}
executor(resolve, reject);//立即执行
}
有了状态、值、原因之后,完善一下 resolve 和 reject,在前文的 A+ 规范中写明了,只有 pending状态的promsie才能改变状态,在其他状态下无法变更为其他状态,所以代码如下:
//...
let self = this; //由于 resolve 和 reject 是window调用的,所以这边保存下this
function resolve(value) { //变成成功态
if (self.status === 'pending') {
self.value = value;
self.status = 'fulfilled';
}
}
function reject(reason) { //变成失败态
if (self.status === 'pending') {
self.reason = reason;
self.status = 'rejected'
}
}
//...
到目前为止 resolve 和 reject 还缺少一些功能,即promise完成或者失败后,需要执行 then 方法中的回调函数,promise 是基于回调函数来实现的,其实就是发布订阅,所以需要通过中间数组来保存回调函数,这边onFulfilled和 onRejected 各需要一个,并且需要再改变状态后执行,如下: ```javascript
function Promise(executor) { this.status = ‘pending’; // pending / fulfilled / rejected this.value = undefined; this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
let self = this;
function resolve(value) { //变成成功态
if (self.status === 'pending') {
self.value = value;
self.status = 'fulfilled';
self.onFulfilledCallbacks.forEach(fn => fn());
}
}
function reject(reason) { //变成失败态
if (self.status === 'pending') {
self.reason = reason;
self.status = 'rejected';
self.onRejectedCallbacks.forEach(fn => fn());
}
}
executor(resolve, reject);//立即执行
}
<a name="WSs0I"></a>
### then 方法
<a name="dUvPr"></a>
#### 入队
如何执行回调队列已经实现了,接下来就是通过 then 方法将成功回调和失败回调放入回调队列中。
- 当 promise 状态为 pending 时,将回调函数放入回调队列中。
- 当 promise 状态为 fulfilled 或者 rejected 时,立即执行。
如下:
```javascript
Promise.prototype.then = function (onFulfilled, onRejected) {
let self = this;
if (self.status === 'fulfilled') {
onFulfilled(self.value);
}
if (self.status === 'rejected') {
onRejected(self.reason);
}
if (self.status === 'pending') {
self.onFulfilledCallbacks.push(function () {
onFulfilled(self.value)
})
self.onRejectedCallbacks.push(function () {
onRejected(self.reason)
})
}
}
实现链式调用
每一个 promise 的状态在改变之后就无法改变了,所以每个 then 方法必须返回一个新的 promise,保证链式调用。
function resolvePromise(promise2, x, resolve, reject){
//... promise 解决程序
}
Promise.prototype.then = function (onFulfilled, onRejected) {
let self = this;
let promise2 = new Promise(function(resolve, reject){
if (self.status === 'fulfilled') {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
}
if (self.status === 'rejected') {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
}
if (self.status === 'pending') {
self.onFulfilledCallbacks.push(function () {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
})
self.onRejectedCallbacks.push(function () {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
})
}
})
return promise2;
}
这边引用了一个 resolvePromise 方法,主要是对返回值的处理,详情见 A+ 规范。同时这边用 setTimeout 包裹起来,原因是在 promise2 实例化的过程中,调用 promise2 是undefined,使用宏任务可以使 执行完主栈代码之后再调用 resolvePromise 方法。
参数的可选性
Promise.prototype.then = function (onFulfilled, onRejected) {
//参数的可选性
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
//...
}
错误处理
Promise.prototype.then = function (onFulfilled, onRejected) {
//参数的可选性
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
let self = this;
let promise2 = new Promise(function (resolve, reject) {
if (self.status === 'fulfilled') {
setTimeout(() => {
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
}
if (self.status === 'rejected') {
setTimeout(() => {
try {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
}
if (self.status === 'pending') {
self.onFulfilledCallbacks.push(function () {
setTimeout(() => {
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
})
self.onRejectedCallbacks.push(function () {
setTimeout(() => {
try {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
})
}
})
return promise2;
}
The Promise Resolution Procedure(resolvePromise 函数)
- 判断 promise2 和 x 是否指向同一对象,是则抛出异常 循环引用。
判断是否是promise,由于要兼容其他人的Promise,所以采用
//满足条件则认为是 promise
if(x !== null && (typeof x === 'object' || typeof x === 'function')){
//...
}
需要判断 resolve和reject是否已经被调用,调用过后忽略后面的调用。
// 这个方法要兼容别人的promise 严谨一些
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) { //防止返回的 promise 和 then 方法返回的promise是同一个
throw new TypeError('循环引用');
}
if (x !== null && (typeof x === 'object' || typeof x === 'function')) { //是 Promise
let called;
try {
let then = x.then; //看看这个对象有没有 then方法,如果有 说明x是promise {then: undefined}
if (typeof then === 'function') {
then.call(x, y => {
if (called) return;
called = true;
//如果返回的是一个promise,resolve的结果可能还是一个promise,递归解析知道这个y是个常量为止
resolvePromise(promise2, y, resolve, reject);
}, r => {
if (called) return; //防止调用成功后又调用失败
called = true;
reject(r);
})
} else {
resolve(x);// {then:{}} {then:123}
}
} catch (e) { //这个then方法是通过 Object.defineProperty 定义的
if (called) return;
called = true; //这个判断为了防止出错后 继续调用成功逻辑
reject(e);
}
} else {
resolve(x); // x 就是普通对象
}
}
完善一下整体的错误处理
```javascript function Promise(executor) { this.status = ‘pending’; // pending / fulfilled / rejected this.value = undefined; this.reason = undefined;
this.onFulfilledCallbacks = []; this.onRejectedCallbacks = []; let self = this;
function resolve(value) { //变成成功态
if (value instanceof Promise) {
return value.then(resolve, reject)
}
if (self.status === 'pending') {
self.value = value;
self.status = 'fulfilled';
self.onFulfilledCallbacks.forEach(fn => fn());
}
}
function reject(reason) { //变成失败态
if (self.status === 'pending') {
self.reason = reason;
self.status = 'rejected';
self.onRejectedCallbacks.forEach(fn => fn());
}
} try {
executor(resolve, reject);//立即执行
} catch (e) {
reject(e)
} }
// 这个方法要兼容别人的promise 严谨一些 function resolvePromise(promise2, x, resolve, reject) { if (promise2 === x) { //防止返回的 promise 和 then 方法返回的promise是同一个 throw new TypeError(‘循环引用’); }
if (x !== null && (typeof x === 'object' || typeof x === 'function')) { //是 Promise
let called; //有的人 promise 既可以调成功 也可以调失败,所以这边兼容下,防止他重复调用
try {
let then = x.then; //看看这个对象有没有 then方法,如果有 说明x是promise {then: undefined}
if (typeof then === 'function') {
then.call(x, y => {
if (called) return;
called = true;
//如果返回的是一个promise,resolve的结果可能还是一个promise,递归解析知道这个y是个常量为止
resolvePromise(promise2, y, resolve, reject);
}, r => {
if (called) return; //防止调用成功后又调用失败
called = true;
reject(r);
})
} else {
resolve(x);// {then:{}} {then:123}
}
} catch (e) { //这个then方法是通过 Object.defineProperty 定义的
if (called) return;
called = true; //这个判断为了防止出错后 继续调用成功逻辑( 这里取 then 的时候可能会报错,但是报错之后可能继续往下走)
reject(e);
}
} else {
resolve(x); // x 就是普通对象
}
}
Promise.prototype.then = function (onFulfilled, onRejected) { //参数的可选性 onFulfilled = typeof onFulfilled === ‘function’ ? onFulfilled : val => val; onRejected = typeof onRejected === ‘function’ ? onRejected : err => { throw err };
let self = this;
let promise2 = new Promise(function (resolve, reject) {
if (self.status === 'fulfilled') {
setTimeout(() => {
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
}
if (self.status === 'rejected') {
setTimeout(() => {
try {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
}
if (self.status === 'pending') {
self.onFulfilledCallbacks.push(function () {
setTimeout(() => {
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
})
self.onRejectedCallbacks.push(function () {
setTimeout(() => {
try {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
})
}
})
return promise2;
}
<a name="JUTfh"></a>
##
<a name="Dm00q"></a>
## 延迟对象
<a name="tVOAj"></a>
### 实现
```javascript
Promise.deferred = function(){
let dtd = {};
dtd.promise = new Promise((resolve, reject)=>{
dtd.resolve = resolve;
dtd.reject = reject;
})
return dtd;
}
使用
let fs = require('fs');
let Promise = require('./promise');
function read(url) {
// 延迟对象
let defer = Promise.deferred(); //{promise, resolve, reject}
fs.readFile(url, 'utf8', function (err, data) {
if (err) return defer.reject(err);
defer.resolve(data);
})
return defer.promise;
}
read('./promise/name.txt').then(data=>{
console.log(data)
},err=>{
console.log(err)
})
A+ 规范测试程序
到此为止,实现了主干代码,并且配置好 延迟对象后,可以执行单元测试对程序进行验证。配置如下:
- 安装包 npm install promises-aplus-tests
- 运行命令 npx promises-aplus-tests 路径
得到结果 872 个测试程序运行通过,即符合A+规范
其他原型方法
Promse.prototype.catch()
Promise.prototype.catch = function(errorCallback){
return this.then(null, errorCallback);
}
Promise.prototype.finally()
Promise.prototype.finally = function (callback) {
return this.then(value => {
return Promise.resolve(callback()).then(()=>{
return value;
})
}, reason => {
return Promise.resolve(callback()).then(()=>{
throw reason;
})
})
}
静态方法
Promise.resolve()
Promise.resolve = function(value){
return new Promise((resolve, reject)=>{
resolve(value);
})
}
Promise.reject()
Promise.reject = function(reason){
return new Promise((resolve, reject)=>{
reject(reason);
})
}
Promise.all()
Promise.all = function (values) {
return new Promise((resolve, reject) => {
let arr = [];
let count = 0;
function processData(key, value) {
arr[key] = value;
if (++count === values.length) {
resolve(arr)
}
}
for (let i = 0; i < values.length; i++) {
let current = values[i];
let then = current.then;
if (then && typeof then === 'function') {
then.call(current, y => {
processData(i, y);
}, reject)
} else {
processData(i, values[i])
}
}
})
}
Promise.race()
Promise.race = function (values) {
return new Promise(function(resolve, reject) {
for (let i = 0; i < values.length; i++) {
let current = values[i];
let then = current.then;
if (then && typeof then === 'function') {
then.call(current, y => {
resolve(y)
}, reject)
} else {
resolve(current);
break;
}
}
})
}
Node 方法 Promise 化
//node中的所有方法 都是错误优先 第二个就是结果 bluebird
function promisify(fn) {// 把方法promise话
return function () {
return new Promise((resolve, reject) => {
//fn.call(null, ...args)
fn(...arguments, function (err, data) {
if (err) reject(err);
resolve(data);
})
})
}
}
function promisifyAll(obj) {
for (let key in obj) {//遍历整个对象 如果是函数的 我就把方法重写
if(typeof obj[key] === 'function'){
obj[key + 'Async'] = promisify(obj[key]); //把每个方法都promise化
}
}
}