规范
Promise的实现遵循Promise/A+的规范:promisesaplus.com/
Promise/A+的测试工具:github.com/promises-ap...
Promise的基础
阅读规范,会发现规范中并没有描述Promise的结构,这是可以理解的,因为规范主要规定了promise的状态转换和then方法的行为。至于具体的实现主要看开发者和使用的语言。
Promise的基础使用:
scss
new Promise((resolve, reject) => {
resolve();
reject();
})
.then()
.catch()
.finally();
分析:
- Promise对象的prototype上有三个方法:
- then
- catch
- finally
- 对象的construct方法接收一个方法,并且方法会接受两个标准的参数 resolve和reject 两个方法
- Promise具备三种状态:pending、fulfilled、reject
- Promise状态变更后会产生value或reason,默认值为undefined
代码的基础结构:
ini
// 定义三个状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED= 'rejected';
class MyPromise {
state = PENDING;
value;
reason;
constructor(func) {
const resolve = (value) => {
if (this.state === PENDING) {
this.state = FULFILLED;
this.value = value;
}
}
const reject = (reason) => {
if (this.state === PENDING) {
this.state = REJECTED;
this.reason = reason;
}
}
try { // 传入的函数执行错误直接抛出错误
func(resolve, reject);
} catch(err) {
reject(err)
}
}
then() {}
catch() {}
finally() {}
}
当然这是最简单的结构,只能实现new操作。
then方法
Promise中then是最核心的方法,catch其实就是then(null, onReject),finally也可以通过then实现:
javascript
finally(callback) {
return this.then(
value => resolve(callback()).then(() => value),
reason => resolve(callback()).then(() => { throw reason })
);
}
关于then方法的行为,规定也给出了详细的说明:
看起来很复杂,我们一点点分析。
基础信息
Promise必须提供then方法用来访问其value或reason。then方法接收两个方法:onFulfilled
、onRejected
。这两个方法分别是Promise成功的回调和失败的回调:
javascript
then(onFulfilled, onRejected) {}
2.2.1 ~ 2.2.3 分别说明了onFulfilled
、onRejected
规范,总结一下:
- 如果不是方法,直接忽略
onFulfilled
必须在Promise状态变更为fulfilled之后才能调用,并且接收的第一个参数必须是Promise的值,不能在状态变更为fulfilled之前被调用onRejected
必须在Promise状态变更为rejected之后才能调用,并且接收的第一个参数必须是Promise失败的原因,不能在状态变更为rejected之前被调用- 回调方法只能被调用一次,不允许超过一次
实现思路:
- 如果状态为pending,需要等待,可以使用一个变量将函数存储起来,在状态变更后再执行对应的函数(就是在resolve和reject执行的时候执行)
- 如果状态已经变更,直接执行对应的回调函数
ini
class MyPromise {
// ...
// 添加存储的变量
fulfilledCallback = null;
rejectedCallback = null;
// ...
const resolve = (value) => {
if (this.state === PENDING) {
this.state = FULFILLED;
this.value = value;
fulfilledCallback && fulfilledCallback(this.value); // 执行回调
}
}
const reject = (reason) => {
if (this.state === PENDING) {
this.state = REJECTED;
this.reason = reason;
rejectedCallback && rejectedCallback(this.reason); // 执行回调
}
}
// ...
then(onFulfilled, onRejected) {
if (this.state === PENDING) {
if (onFulfilled instanceof Function) {
fulfilledCallback = onFulfilled;
}
if (onRejected instanceof Function) {
rejectedCallback = onRejected;
}
} else if (this.state === FULFILLED) {
if (onFulfilled instanceof Function) {
onFulfilled();
}
} else if (this.state === REJECTED) {
if (onRejected instanceof Function) {
onRejected();
}
}
}
}
2.2.4 onFulfilled
、onRejected
只有在执行环境堆栈仅包含平台代码时才可被调用。这里的"platfom code"指的是引擎、环境和Promise实现代码,其实就是指代码执行的平台对Promise的实现。在实践中,需要保证onFulfilled
、onRejected
是异步执行(相对于then),它们的执行时机应该在调用then的事件循环之后的新的执行任务中。在JavaScript中,使用的是微任务来实现。
这个点说白了就是onFulfilled
、onRejected
必须在当前执行栈清空后才能执行。
这个属于平台底层实现的机制,这里可以用setTimeout来模拟异步的效果(注意:setTimeout是宏任务,promise.then是微任务,这里只是模拟)
2.2.5 onFulfilled
、onRejected
必须被作为函数调用(即没有this)。作为函数调用是指这样的调用方式:
scss
onFulfilled();
onRejected();
区别于其他的调用方式:
scss
// 作为方法
xx.onRejected();
// 作为构造函数
new onRejected();
// 指定this
onRejected.call(xx);
onRejected.apply(xx)
这些方式都会指定this。
不指定this,那么函数的this就会绑定到全局对象(严谨模式下是undefined),这样不会将当前的Promise实例产生依赖,增加代码的灵活性,同时避免出现副作用(例如回调函数将实例返回,会导致链式调用出现问题)。
2.2.6 then
可以被同一个Promise多次调用:
- Promise成功后,所有的onFulfilled按照注册的顺序依次调用
- Promise失败后,所有的onRejected按照注册的顺序依次调用
这里指的是这种情况:
typescript
const p = new Promise((resovle, reject) => {
resovle();
});
p.then(() => {console.log(1);}); // 1
p.then(() => {console.log(2);}); // 2
p.catch(() => {console.log(3);});
p.catch(() => {console.log(4);});
实现思路: 如果Promise的状态已经变更了,直接执行回调函数即可。如果未变更,将存储回调函数的变量改成数组,收集阶段入列,执行阶段遍历执行
scss
// ...
class MyPromise {
// ...
fulfilledCallbackList = [];
rejectedCallbackList = [];
// ...
const resolve = (value) => {
// ...
fulfilledCallbackList.forEach((fn) => {
fn(this.value); // 执行回调
});
}
// ...
then(onFulfilled, onRejected) {
if (this.state === PENDING) {
if (onFulfilled instanceof Function) {
fulfilledCallbackList.push(
() => {
setTimeout(() => {
onFulfilled();
})
}
)
}
if (onRejected instanceof Function) {
rejectedCallbackList.push(
() => {
setTimeout(() => {
onRejected();
})
}
);
}
}
// ...
}
}
2.2.7 then必须返回一个Promise:
ini
promise2 = promise1.then(onFulfilled, onRejected);
- 返回的promise是一个新的promise
- 如果
onFulfilled
或者onRejected
返回了值 x,执行Promise的解决过程[[Resolve]](promise2, x)
。Promise的解决过程是一个核心的逻辑,会根据x来处理返回值。 - 如果
onFulfilled
或者onRejected
抛出错误e,promise2的状态为rejected,错误原因为e - 如果
onFulfilled
不是方法并且promise1的状态是fulfilled,promise2的状态必须为fulfilled,使用promise1的value作为返回值 - 如果
onRejected
不是方法并且promise1的状态是rejected,promise2的状态必须为rejected,使用promise1的reason作为reason
实现思路:
- 返回一个新的promise
- 在函数执行时增加错误捕获,如果有错误直接抛出错误
- 在
onFulfilled
和onRejected
不是方法的情况下,promise2跟随pormise1的状态,并返回对应的值。将两个参数默认值置为返回promise1的value和抛出promise1的错误 - 实现一个Promise解决过程方法:
resolvePromise
后面有很多地方都需要判断是否为函数,所以我们先增加一个判断函数类型的方法:
javascript
function isFunction(func) {
return typeof func === 'function';
}
这里必须用typeof,如果用instanceof的话,会出现将继承Function.prototype的对象判断为方法的情况,会出现方法无法执行挂掉的情况。
then的代码:
ini
then(onFulfilled = null, onRejected = null) {
onFulfilled = isFunction(onFulfilled) ? onFulfilled : () => this.value;
onRejected = isFunction(onRejected) ? onRejected : () => { throw this.reason };
let promise2 = new MyPromise((resolve, reject) => {
// 等待中,收集回调
if (this.state === PENDING) {
this.fulfilledCallbackList.push(
() => {
setTimeout(() => {
try {
const v = onFulfilled(this.value);
resolvePromise(promise2, v, resolve, reject);
} catch (e) {
reject(e);
}
});
}
);
this.rejectedCallbackList.push(
() => {
setTimeout(() => {
try {
const v = onRejected(this.reason);
resolvePromise(promise2, v, resolve, reject);
} catch (e) {
reject(e);
}
});
}
);
} else if (this.state === FULFILLED) {
setTimeout(() => {
try {
const v = onFulfilled(this.value);
resolvePromise(promise2, v, resolve, reject);
} catch (e) {
reject(e);
}
});
} else if (this.state === REJECTED) {
setTimeout(() => {
try {
const v = onRejected(this.reason);
resolvePromise(promise2, v, resolve, reject);
} catch (e) {
reject(e);
}
});
}
});
return promise2;
}
Promise的解决过程
Promise的解决过程是一个抽象的操作,表示为:[[Resolve]](Promise, x)
,通过上面then的说明,我们知道这个方法接收两个参数,其中Promise是then要返回的新promise(也就是上面代码中的promise2),x为需要返回的值。
接下来我们根据规范实现一个resolvePromise
方法。
先看看解决过程的步骤:
从说明中可以总结几个点:
- promise和x不能是一个,如果是同一个抛出错误,因为promise要返回x,如果是同一个就违反了返回新的promise实例的规则
javascript
let p = new Promise((resolve, reject) => {
resolve();
});
let p2 = p.then(() => { return p2;}) // TypeError: Chaining cycle detected for promise
then
都是返回promise2,所以不管x是什么情况,不管中间有多少个层级的嵌套,最终都要改变promise2的状态和对应值或原因。所以resolvePromise
需要接收可以修改promise2状态的resolve
和reject
两个方法,否则返回的promise2会一直处于pending的状态。- 在所有有返回值的地方全部都需要使用
resolvePromise
来处理返回值,因为需要x产生的值也可能是promise或thenable。
ini
function resolvePromise(promise, x, resolve, reject) {
// x等于promise,抛出错误
if (x === promise) {
reject(new TypeError('Chaining cycle detected for promise'));
} else if(x instanceof MyPromise) { // 如果是promise
if (x.state === PENDING) {
x.then(
(y) => resolvePromise(promise, y, resolve, reject),
e => reject(e)
);
} else if (x.state === FULFILLED) {
resolvePromise(promise, x.value, resolve, reject);
} else if (x.state === REJECTED) {
reject(x.reason);
}
} else if (x && typeof x === 'object' || isFunction(x)) {
let called = false; // 标识使用情况
try {
let then = x.then;
if (isFunction(then)) {
then.call(
x,
(y) => {
if (called) return;
called = true;
resolvePromise(promise, y, resolve, reject);
},
(r) => {
if (called) return;
called = true;
reject(r);
}
)
} else {
if (called) return;
called = true;
resolve(x);
}
} catch(e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
因为resolvePromise
存在递归调用,而且不希望resolvePromise
被暴露出去,所以这里没将其放在class中。
catch方法和finally方法
这两个方法其实是对then方法的延伸,让Promise的使用变得更简单。
catch的实现比较简单,直接复用then方法就行了
kotlin
catch(callback) {
return this.then(null, callback)
}
finally的实现就比较复杂了,finally的特性如下:
- 会返回一个新的Promise实例
- 会将上一个成功或失败的结果透传到后面的方法
- 如果callback执行失败或者抛出错误,则需要返回错误
typescript
const p = new Promise((resolve, reject) => {
reject(111);
});
p.finally(() => {
return 333;
}).catch((e) => {
console.log(e); // 111
});
p.catch(() => {
return 333;
}).finally(()
.catch((e) => {
console.log(e); // 333
});
const p = new Promise((resolve, reject) => {
reject(123);
});
p.finally(() => {
throw 333;
}).catch((e) => {console.log(e);}); // 333
思路:
- 创建一个新的Promise,在新的Promise中执行callback,这样可以防止callback影响现有的Promise
- 在原Promise实例的then中,执行上面的操作,这样就可以保证在原Promise状态变更后都会执行callback
- 利用then会传递Promise的值和原因的特点,就可以实现透传
- callback报错需要传递错误,因为是在返回的新Promise中执行,只要callback抛出错误,也会被正常捕获传递
javascript
finally(callback) {
return this.then(
value => {
return new MyPromise((resolve) => {
resolve(callback());
}).then(() => {
return value
});
},
reason => {
return new MyPromise((resolve) => {
resolve(callback());
}).then(() => {
throw reason
});
}
)
}
最终代码
ini
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function isFunction(func) {
return typeof func === 'function';
}
function resolvePromise(promise, x, resolve, reject) {
// x等于promise,抛出错误
if (x === promise) {
reject(new TypeError('Chaining cycle detected for promise'));
} else if(x instanceof MyPromise) { // 如果是promise
if (x.state === PENDING) {
x.then((y) => {
resolvePromise(promise, y, resolve, reject)
}, e => reject(e));
} else if (x.state === FULFILLED) {
resolvePromise(promise, x.value, resolve, reject);
} else if (x.state === REJECTED) {
reject(x.reason);
}
} else if (x && typeof x === 'object' || isFunction(x)) {
let called = false; // 标识使用情况
try {
let then = x.then;
if (isFunction(then)) {
then.call(
x,
(y) => {
if (called) return;
called = true;
resolvePromise(promise, y, resolve, reject);
},
(r) => {
if (called) return;
called = true;
reject(r);
}
)
} else {
if (called) return;
called = true;
resolve(x);
}
} catch(e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
class MyPromise {
state = PENDING;
value;
reason;
fulfilledCallbackList = [];
rejectedCallbackList = [];
constructor(func) {
const resolve = (value) => {
if (this.state === PENDING) {
this.state = FULFILLED;
this.value = value;
// 执行成功回调
this.fulfilledCallbackList.forEach(fn => fn());
}
}
const reject = (reason) => {
if (this.state === PENDING) {
this.state = REJECTED;
this.reason = reason;
// 执行失败回调
this.rejectedCallbackList.forEach(fn => fn());
}
}
try { // 传入的函数执行错误直接抛出错误
func(resolve, reject);
} catch (err) {
reject(err)
}
}
then(onFulfilled = null, onRejected = null) {
onFulfilled = isFunction(onFulfilled) ? onFulfilled : () => this.value;
onRejected = isFunction(onRejected) ? onRejected : () => { throw this.reason };
let promise2 = new MyPromise((resolve, reject) => {
// 等待中,收集回调
if (this.state === PENDING) {
this.fulfilledCallbackList.push(
() => {
setTimeout(() => {
try {
const v = onFulfilled(this.value);
resolvePromise(promise2, v, resolve, reject);
} catch (e) {
reject(e);
}
});
}
);
this.rejectedCallbackList.push(
() => {
setTimeout(() => {
try {
const v = onRejected(this.reason);
resolvePromise(promise2, v, resolve, reject);
} catch (e) {
reject(e);
}
});
}
);
} else if (this.state === FULFILLED) {
setTimeout(() => {
try {
const v = onFulfilled(this.value);
resolvePromise(promise2, v, resolve, reject);
} catch (e) {
reject(e);
}
});
} else if (this.state === REJECTED) {
setTimeout(() => {
try {
const v = onRejected(this.reason);
resolvePromise(promise2, v, resolve, reject);
} catch (e) {
reject(e);
}
});
}
});
return promise2;
}
catch(callback) {
return this.then(null, callback)
}
finally(callback) {
return this.then(
value => {
return new MyPromise((resolve) => {
resolve(callback());
}).then(() => {
return value
});
},
reason => {
return new MyPromise((resolve) => {
resolve(callback());
}).then(() => {
throw reason
});
}
)
}
}
测试代码
github.com/promises-ap...提供了872测试用例。要使用它来测试,需要先看下要求:
按这里的要求,promise库需要提供一个简单接口适配器,提供几种方法:
- resolved(value) 创建返回value的promise
- rejected(reason) 创建返回错误reason的promise
- deferred() 创建一个由 { promise, resolve, reject } 组成的对象
但是我翻阅了一下他的源码,发现初始化里面有这个方法:
如果提供的适配器,没有resolved和rejected的话,它会自己生成一个,所以我们只要提供deferred方法即可。
按照要求,我们添加这段代码:
ini
module.exports = {
deferred() {
let dfd = {};
dfd.promise = new MyPromise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
}
然后,我们需要安装测试工具:
全局安装
npm install promises-aplus-tests -g
或者安装在当前项目
npm install promises-aplus-tests -S -D
在package.json中添加:
json
"scripts": {
"test": "promises-aplus-tests promise.js"
}
执行测试,在promise库的目录下执行命令:
arduino
// 全局
promises-aplus-tests promise.js
// 当前项目
npm run test
执行成功: