Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值。
promise的基本用法
const p = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('do something...');
const value = 'some result';
resolve(value);
}, 1000);
});
p.then(
value => {
console.log(value);
},
err => {
console.log(err);
}
);
// promise 也可以链式调用
new Promise((resolve, reject) => {
setTimeout(() => {
console.log('do something...');
const value = 'some result';
resolve(value);
}, 1000);
}).then(
value => {
console.log(value);
},
err => {
console.log(err);
}
);
实现一个符合Promise/A+规范的Promise
- Promise默认是一个类,用的时候需要new,创建的实例上都有一个then方法,在new过程中需要传入一个executor执行器参数
- pomise有三个状态,分别为pending(等待态)、fulfilled(成功态)和rejected(失败态)
- Promise中有一个value属性用来描述成功的返回值,reason用描述失败的原因
- promise中如果出现异常也会执行失败的逻辑
- 当promise状态是pending的时候,可以转为fulfilled或者rejected,过程是不可逆的,也就是不能从fulfilled或者rejected转为其他状态
- executor会立即执行,并且接受resolve和reject两个参数,这两个参数都是函数类型,调用resolve可以将promise转为fulfilled状态,调用reject可以将promise转为rejected状态。
参照上面的解析,我们可以写出第一版最初始的promise
// 定义promise状态
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class Promise {
constructor(executor) {
this.value = undefined;
this.reason = undefined;
this.status = PENDING;
const resolve = value => {
// 只能由pending态转为其他态
if (this.status !== PENDING) return;
this.status = FULFILLED;
this.value = value;
};
const reject = reason => {
// 只能由pending态转为其他态
if (this.status !== PENDING) return;
this.status = REJECTED;
this.reason = reason;
};
try {
executor(resolve, reject);
} catch (e) {
// 执行executor过程中出错
reject(e);
}
}
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value);
}
if (this.status === REJECTED) {
onRejected(this.reason);
}
}
}
但是现在实现的代码非常脆弱,只能实现状态变为fulfilled或者rejected时再调用then,但是在Promises/A+规范中,then可以随时调用,并且同一个实例可以多次调用then。所以我们稍稍改变一些代码,用两个数组来分别存放pending状态时调用then的onFulfilled和onRejected回调函数,当调用resolve改变promise状态时,再分别从数组中取出回调函数依次执行。这样就能解决这两个问题。
// 定义promise状态
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class Promise {
constructor(executor) {
this.value = undefined;
this.reason = undefined;
this.status = PENDING;
// 用来存放成功的回调函数
this.onFulfilledCallbacks = [];
// 用来存放失败的回调函数
this.onRejectedCallbacks = [];
const resolve = value => {
// 只能由pending态转为其他态
if (this.status !== PENDING) return;
this.status = FULFILLED;
this.value = value;
this.onFulfilledCallbacks.forEach(cb => cb(this.value));
};
const reject = reason => {
// 只能由pending态转为其他态
if (this.status !== PENDING) return;
this.status = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach(cb => cb(this.reason));
};
try {
executor(resolve, reject);
} catch (e) {
// 执行executor过程中出错
reject(e);
}
}
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value);
}
if (this.status === REJECTED) {
onRejected(this.reason);
}
if (this.status === PENDING) {
// 有可能调用then的时候既没成功也没失败,将回调存起来(发布订阅模式)
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
}
}
这样看起来很完美,但是Promises/A+规范要求then方法能链式调用,并且
- promise中then传入的方法, 返回的结果不是一个promise,会将这个结果传递给下一个then的成功中
- 如果then传入的方法在执行的时候出错了,会执行下一次then的失败
- 如果then传入的方法执行返回的是一个promise,那么会根据promise的状态来决定走下一次then的成功还是失败,成功的值和失败的原因以当前这个promise为准
所以这里我们改造一下then方法,支持调用回调时返回一个不是promise的情况:
then(onFulfilled, onRejected) {
// 为了支持链式调用,我们这里返回一个新的promise实例
const p = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
try {
const x = onFulfilled(this.value);
resolve(x);
} catch (e) {
// 执行回调的时候出错,直接reject
reject(e);
}
}
if (this.status === REJECTED) {
try {
const x = onRejected(this.reason);
resolve(x);
} catch (error) {
// 执行回调的时候出错,直接reject
reject(e);
}
}
if (this.status === PENDING) {
this.onFulfilledCallbacks.push(() => {
try {
const x = onFulfilled(this.value);
resolve(x);
} catch (e) {
// 执行回调的时候出错,直接reject
reject(e);
}
});
this.onRejectedCallbacks.push(() => {
try {
const x = onRejected(this.reason);
resolve(x);
} catch (e) {
// 执行回调的时候出错,直接reject
reject(e);
}
});
}
});
return p;
}
下面我们来实现promise中最复杂的情况,也就是当执行onFulfilled或者onRejected回调函数时返回一个promise的情况,我们定义一个resolvePromise函数,用来专门处理执行onFulfilled或者onRejected后的剩余处理:
function resolvePromise(promise, x, resolve, reject) {
// 用x的值来决定promise走resolve还是reject
// 不能自己等待自己完成
if (promise === x) {
return reject(new TypeError(`TypeError: Chaining cycle detected for promise #<Promise> `));
}
// 思考: 这里能不能使用 x instanceof Promise 呢?
// 我们要考虑和不同人写的promise可以相互兼容,所以这里这里要按照规范来实现,保证promise之间可以相互调用, 所以这里不能用 x instanceof Promise
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
// 这个then方法可能是别人实现的promise中的then,没有处理同时调用成功和失败,
// 用一个变量来标识 只能调用resolve或reject, 不能两者都调用
let called = false;
try {
const then = x.then;
if (typeof then === 'function') {
// 有可能 x = { then: 111 }
// 执行结果为一个promise实例时
then.call(
x,
v => {
if (called) return;
called = true;
// 递归判断是否任然返回的是promise
resolvePromise(promise, v, resolve, reject);
},
r => {
if (called) return;
called = true;
reject(r);
}
);
} else {
// 依旧是普通值
resolve(x);
}
} catch (e) {
// 这里我们为什么要用try catch呢,原因是其他promise的实现中then可能是用defineProperty定义的, 当调用x.then会出错, 这里为了更加严谨最好包一层 try catch
/**
Object.defineProperty(x, 'then', {
get() {
throw new Error()
}
})
*/
if (called) return;
called = true;
reject(e);
}
} else {
// 执行返回结果为普通值
resolve(x);
}
}
then(onFulfilled, onRejected) {
// 为了支持链式调用,我们这里返回一个新的promise实例
const p = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
// try {
// const x = onFulfilled(this.value);
// // 这里不能直接这样写,因为此时p还没有值,我们不能在new的过程中获取到实例
// // resolvePromise(p, x, resolve, reject);
// // 我们在下一个事件循环tick中就能获取到p实例了
// setTimeout(() => {
// resolvePromise(p, x, resolve, reject);
// });
// } catch (e) {
// reject(e);
// }
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(p, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}
if (this.status === REJECTED) {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(p, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}
if (this.status === PENDING) {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(p, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(p, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
});
return p;
}
上面我们使用了setTimeout,这样就能在下一个事件循环tick中获取到返回的promise的实例了,但是setTimeout会将回调放入宏任务队列。我们都知道原生的promise是将其放入微任务队列中的,所以这里我们可以用queueMicrotask来代替setTimeout。
到这里,Promise的实现基本就算完成了,但是还有一个细节要注意,当调用then时,可把第一个参数设置undefined,只写onRejected, 这样在后面的then中也能实现获取到上一个promise resolve的value。同样我们可以不写then中的第二个参数,在后面的then中也能获取到上一个promise reject的reason,实现穿透效果。
主要实现一个方法可以层层传递:
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e };
最终我们就实现了一个完整版的遵循Promises/A+规范的promise,注意我们平时用到的Promise.resolve、Promise.reject、Promise.all、Promise.race、finally等方法不属于Promises/A+规范中的定义
这里我们也来实现一下:
resolve
static resolve(value) {
return new Promise((resolve, reject) => {
resolve(value);
});
}
reject
static reject(value) {
// 默认创建一个失败的promise
return new Promise((resolve, reject) => {
reject(value);
});
}
catch
catch(errCallback) {
return this.then(null, errCallback);
}
all
static all = function (promises) {
let result = [];
let times = 0;
return new Promise((resolve, reject) => {
function processResult(data, index) {
result[index] = data; // 映射结果
if (++times == promises.length) {
resolve(result);
}
}
for (let i = 0; i < promises.length; i++) {
let promise = promises[i];
Promise.resolve(promise).then(data => {
processResult(data, i);
}, reject);
}
});
};
race
static race = function (promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
let promise = promises[i];
Promise.resolve(promise).then(resolve, reject);
}
});
};
finally
finally(finallyCallback) {
return this.then(
data => {
Promise.resolve(finallyCallback()).then(() => data);
},
err => {
return Promise.resolve(finallyCallback()).then(() => {
throw err;
});
}
);
}
allSettled
static allSettled = function (promises) {
let result = [];
let times = 0;
return new Promise((resolve, reject) => {
function processResult(data, index, status) {
result[index] = { status, value: data };
if (++times == promises.length) {
resolve(result);
}
}
for (let i = 0; i < promises.length; i++) {
let promise = promises[i];
Promise.resolve(promise).then(
data => {
processResult(data, i, 'fulfilled');
},
err => {
processResult(err, i, 'rejected');
}
);
}
});
};
最终版的代码如下:
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
function resolvePromise(promise, x, resolve, reject) {
if (promise === x) {
return reject(new TypeError(`TypeError: Chaining cycle detected for promise #<Promise> `));
}
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
let called = false;
try {
const then = x.then;
if (typeof then === 'function') {
then.call(
x,
v => {
if (called) return;
called = true;
resolvePromise(promise, v, resolve, reject);
},
r => {
if (called) return;
called = true;
reject(r);
}
);
} else {
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
class Promise {
constructor(executor) {
this.value = undefined;
this.reason = undefined;
this.status = PENDING;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = value => {
// 这里我们添加一个规范外的逻辑 让value值是promise的话可以进行一个解析
if(value instanceof Promise){
// 递归解析值
return value.then(resolve, reject)
}
if (this.status !== PENDING) return;
this.status = FULFILLED;
this.value = value;
this.onFulfilledCallbacks.forEach(cb => cb(this.value));
};
const reject = reason => {
if (this.status !== PENDING) return;
this.status = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach(cb => cb(this.reason));
};
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected =
typeof onRejected === 'function'
? onRejected
: e => {
throw e;
};
const p = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(p, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}
if (this.status === REJECTED) {
queueMicrotask(() => {
try {
const x = onRejected(this.reason);
resolvePromise(p, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}
if (this.status === PENDING) {
this.onFulfilledCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(p, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
this.onRejectedCallbacks.push(() => {
queueMicrotask(() => {
try {
const x = onRejected(this.reason);
resolvePromise(p, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
});
return p;
}
catch(errCallback) {
return this.then(null, errCallback);
}
finally(finallyCallback) {
return this.then(
data => {
Promise.resolve(finallyCallback()).then(() => data);
},
err => {
return Promise.resolve(finallyCallback()).then(() => {
throw err;
});
}
);
}
static resolve(value) {
return new Promise((resolve, reject) => {
resolve(value);
});
}
static reject(value) {
return new Promise((resolve, reject) => {
reject(value);
});
}
static all = function (promises) {
let result = [];
let times = 0;
return new Promise((resolve, reject) => {
function processResult(data, index) {
result[index] = data; // 映射结果
if (++times == promises.length) {
resolve(result);
}
}
for (let i = 0; i < promises.length; i++) {
let promise = promises[i];
Promise.resolve(promise).then(data => {
processResult(data, i);
}, reject);
}
});
};
static race = function (promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
let promise = promises[i];
Promise.resolve(promise).then(resolve, reject);
}
});
};
static allSettled = function (promises) {
let result = [];
let times = 0;
return new Promise((resolve, reject) => {
function processResult(data, index, status) {
result[index] = { status, value: data };
if (++times == promises.length) {
resolve(result);
}
}
for (let i = 0; i < promises.length; i++) {
let promise = promises[i];
Promise.resolve(promise).then(
data => {
processResult(data, i, 'fulfilled');
},
err => {
processResult(err, i, 'rejected');
}
);
}
});
};
}
module.exports = Promise;
最后来测试一下我们写的代码是否完全符合Promises/A+规范,可以安装 promises-aplus-tests 这个包进行测试。
我们还需要添加一些 promises-aplus-tests测试需要用的代码:
// 为了测试是否符合规范需要导出的
Promise.deferred = function () {
const dfd = {};
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
};
最后执行
npx promises-aplus-tests myPromise.js
可以看到我们通过了所有的测试用例~