准备工作
手写代码之前,我们先了解一下 Promise ,知道得越多,写出来的代码越健壮。
Promise A+ 规范
- 官方英文地址:promisesaplus.com/
- 中文翻译可参考 malcolmyu.github.io/malnote/201…
很多人会按照这个规范来写,由于我想复习一下 Promise 的相关知识,所以我不会按照这个规范来写,而是根据我所知的知识来写,这样,如果代码跑起来有不同之处,就可以快速发现,达到复习+学习的目的。
Promise 特性:
then/catch会返回一个Promise。Promise状态确定后,不可再变化。- 如果
Promise/then传入的参数不是函数,将会透传。 Promise通过resolve/reject来更改状态,then回调函数通过获得同步返回值来返回状态。- 链式调用时,下一个
then可获得上一个then的返回值。 new Promise(cb)中的cb会立刻执行,但.then(thenCb)中的thenCb不会马上执行,也不会推到微任务队列中,而是等待Promise resolve后,才会推到微任务队列中去。同理,根据特性4可知,下一个then的回调被推入微任务队列的时机是上一个then resolve后。- 根据特性6可知,
then的作用是注册回调函数 ,在resolve前then注册的回调函数将会储存在Promise中 ,resolve后将会遍历通过then注册回调函数,依次将他们推到微任务队列中去,事件循环将回调函数取出来,执行。
下面解释一下,特性1 , 4,7。
第1点: then / catch 会返回一个 Promise 这个会影响两方面:
-
链式调用。返回一个
Promise对象,才能将下一个then挂在上面 -
请看以下代码:
// 代码来自 [Promise 链](https://zh.javascript.info/promise-chaining) let promise = new Promise(function(resolve, reject) { setTimeout(() => resolve(1), 1000); }); promise.then(function(result) { alert(result); // 1 return result * 2; }); promise.then(function(result) { alert(result); // 1 return result * 2; }); promise.then(function(result) { alert(result); // 1 return result * 2; }); // 代码来自 [Promise 链](https://zh.javascript.info/promise-chaining) new Promise(function(resolve, reject) { setTimeout(() => resolve(1), 1000); }) .then(function(result) { alert(result); return result * 2; }) .then(function(result) { alert(result); return result * 2; }) .then(function(result) { alert(result); return result * 2; });运行后,会发现第一个
Promise的结果是 1,1,1,而第二个Promise运行的结果是 1,2,4。根据特性5可知,第一个
Promise看似链式调用却不是,第二个才是链式调用。因为第一个Promise的then都是挂在promise本身上的,而第二个Promise的then是依次挂在前一个then返回的Promise上的,根据特性5,下一个then可以获得上一个then的返回值。
第4点: Promise 通过 resolve / reject 来更改状态,then 回调函数通过获得同步返回值来返回状态
什么意思呢?
Promise 通过 resolve / reject 来更改状态,这个容易理解,也很常见。
then 回调函数通过获得同步返回值来返回状态,这个的意思就是,then 回调函数只要完成,这个 then 的状态就等同于 resolve 。如:
new Promise((resolve) => resolve(123)).then(res => console.log(res));
当 console.log(res) 执行完毕,这个 then 的状态就是完成了。
这里额外提示两个信息,其一:
new Promise((resolve) => resolve(123))
// then1
.then(res => {
new Promise(resolve => resolve(456)).then(_res => console.log(_res));
return res;
})
// then2
.then(res => console.log(res, 'then2'))
那么 then2 什么时候执行呢?
答案是:等待 then1 中的 Promise resolve 后才会执行。因为 Promise resolve 后返回一个 Promise ,此时这个 Promise 就是同步返回值了。
所以同步返回值的意思应该是:
- 如果是同步代码,那执行完毕同步代码即可
- 如果是
Promise,那要等待这个Promise resolve后即可
第2点中,为什么不说是微任务呢?因为就浏览器端,微任务就 Promise 和 MutationObserver 两个 ,其中 Promise resolve 会返回一个 Promise ,可视为同步返回值,MutationObserver 这个我暂时不了解,所以这个定义的范围我就缩小点,只将已知情况归纳进去。
其二:
new Promise(resolve => resolve(1))
.then(res => {
return new Promise(resolve => resolve(2)).then(_res => {
return _res * 2
}).then(_res => {
return _res * 3;
})
}).then(res => console.log(res));
那么外面的 Promise 的第二个 then 什么时候会执行呢?会打印什么内容呢?
答案是:等待里面的 Promise 完全执行完毕后,才会执行。打印 12。
第二个例子跟第一个例子的区别:就是 Promise 是否作为返回值返回。
那这个区别的实质是什么呢?
我的理解是:
then 注册的回调函数不可以返回一个不确定的值。因为 then 会返回一个 Promise (特性1) ,这个不确定的值返回给这个 Promise 后,会导致 Promise 无法 resolve 进而导致下一个 then 流程无法进行。
所以 then 注册的回调函数中如返回一个 Promise ,那就将它完全执行完毕,得到最终值。然后将这个值返回给 then 创建的 Promise 。当然,完全执行完这个 Promise ,也是遵循着 Promise特性 和 事件循环 的。如:
new Promise(resolve => resolve(1))
.then(res => {
return new Promise(resolve => resolve(2)).then(_res => {
console.log('inner then1')
return _res * 2
}).then(_res => {
console.log('inner then2')
return _res * 3;
})
}).then(res => console.log(res));
new Promise(resolve => resolve('test')).then(res => {
console.log(res, 'test then1');
return res;
}).then(res => {
console.log(res, 'test then2');
return res;
})
// test test then1
// inner then1
// test test then2
// inner then2
// 12
所以,对于同步返回值的意思,我们重新归纳一下:
then返回值一定是一个确定的值- 如果是同步代码,那执行完毕同步代码即可
- 如果执行的代码是
Promise(非返回值),那要等待这个Promise resolve后(即这个Promise的then注册的回调事件执行完后返回一个promise)即可 - 如果
then返回值是Promise,那么将会将这个Promise完全执行完毕(所有then注册的回调函数都执行完毕),得到确定的值,这个值就是这个then的同步返回值
第7点:根据特性6可知,then 的作用是注册回调函数 ,在 resolve 前 then 注册的回调函数将会储存在 Promise 中 , resolve 后将会遍历通过 then 注册回调函数,依次将他们推到微任务队列中去,事件循环将回调函数取出来,执行。
先看一段代码,这段代码在上文也有:
// 代码来自 [Promise 链](https://zh.javascript.info/promise-chaining)
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
当 Promise resolve 后,可以看到 then 给变量 Promise 注册了三个回调事件。所以他们会被依次推进微任务队列中,等待事件循环取出来执行。
而下面这段代码就不一样了:
// 代码来自 [Promise 链](https://zh.javascript.info/promise-chaining)
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
})
.then(function(result) {
alert(result);
return result * 2;
})
.then(function(result) {
alert(result);
return result * 2;
})
.then(function(result) {
alert(result);
return result * 2;
});
可以看到当 Promise resolve 后,只有一个 then 是挂在该 promise 上的。
且当第一个 then 完成后,将会返回一个新的 Promise ,所以第二个 then 注册的回调事件,是挂在新的 Promise 上的。
所以这两段代码的 then 执行顺序其实是不一样的:
第一段代码,三个then 是被同时推进微任务队列的(因为同挂在一个Promise 上)
第二段代码可以看到,then 是依次挂在上一个 then 返回的 Promise 上的,所以他们是会被依次推进微任务队列中(也就是说上个回调事件推进微任务中执行完了后,下一个 then 注册的回调事件才被推入微任务队列中,此处忽略其他代码的执行,只关注 Promise 的情况)。
乞丐版的实现
function _Promise(cb) {
this.status = 'pending';
this.value = '';
this.reason = '';
let self = this;
this.resolve = function (value) {
// 特性2 `promise` 状态确定后,不可再变化。
if (self.status === 'pending') {
self.value = value;
self.status = 'fulfilled';
}
}
this.reject = function (reason) {
// 特性2 `promise` 状态确定后,不可再变化。
if (self.status === 'pending') {
self.reason = reason;
self.status = 'rejected';
}
}
cb(this.resolve, this.reject);
}
_Promise.prototype.then = function (cb) {
if (typeof cb === 'function') {
if (this.status === 'fulfilled') {
cb(this.value);
}
}
return this;
}
new _Promise(resolve => resolve(123)).then(res => console.log(res));// 123
显而易见,这种乞丐版的实现,功能非常有限。主要是返回一个新的 Promise 这个重要特性没体现出来。虽然可以链式调用,但所有的 then 均是挂载在 _Promise 实例上,即以下两段代码的效果是等同:
// 第一段
let p = new _Promise(resolve => resolve(123));
p.then(res => console.log(res, 'p1'));
p.then(res => console.log(res, 'p1'));
// 第二段
new _Promise(resolve => resolve(123)).then(res => console.log(res, 'p2')).then(res => console.log(res, 'p2'));
// _Promise: 123 p1 123 p1 123 p2 123 p2
// Promise:123 p1 123 p1 123 p2 undefined p2
丐中丐的实现
function _Promise(cb) {
this.status = 'pending';
this.value = '';
this.reason = '';
// 缓存方法
this.resolveCache = [];
this.rejectCache = [];
let self = this;
this.resolve = function (value) {
// 特性2 `promise` 状态确定后,不可再变化。
if (self.status === 'pending') {
self.value = value;
self.status = 'fulfilled';
while (self.resolveCache.length) {
const fn = self.resolveCache.shift();
const result = typeof fn === 'function' ? fn(self.value) : fn;
self.value = result;
}
}
}
this.reject = function (reason) {
// 特性2 `promise` 状态确定后,不可再变化。
if (self.status === 'pending') {
self.reason = reason;
self.status = 'rejected';
while (self.rejectCache.length) {
const fn = self.rejectCache.shift();
const result = typeof fn === 'function' ? fn(self.reason) : fn;
self.reason = result;
}
}
}
cb(this.resolve, this.reject)
return this;
}
_Promise.prototype.then = function (cb) {
if (typeof cb === 'function') {
if (this.status === 'fulfilled') {
cb(this.value);
}
if (this.status === 'pending') {
this.resolveCache.push(cb);
this.rejectCache.push(cb);
}
}
return this;
}
new _Promise(resolve => setTimeout(() => resolve(123), 1000))
.then(res => console.log(res))
.then(res => console.log(res, 'then2'))
// Promise: delay 1s 123 undefined then2
// _Promise: delay 1s 123 undefined then2
缺点依旧在,只是增加了 then 等待 resolve 执行再执行的功能。依旧是乞丐版。
贫民版
以上两个版本,只是热身,下面才是实现。
function _Promise(cb) {
this.status = 'pending';
this.value = '';
this.reason = '';
// 缓存方法
this.resolveCache = [];
this.rejectCache = [];
let self = this;
this.resolve = function (value) {
function run() {
// 特性2 `promise` 状态确定后,不可再变化。
if (self.status === 'pending') {
// 特性5 链式调用时,下一个 `then` 可获得上一个 `then` 的返回值。
self.value = value;
self.status = 'fulfilled';
while (self.resolveCache.length) {
const fn = self.resolveCache.shift();
fn(self.value);
}
}
}
// 特性6,7的体现,如不是这种方式(像 run()),将会连续执行这个promise的 then 注册的所有回调事件然后再到其他的 promise 事件
// 应该是微任务实现,这里用 setTimeout 模拟
setTimeout(run);
}
this.reject = function (reason) {
function run() {
// 特性2 `promise` 状态确定后,不可再变化。
if (self.status === 'pending') {
self.reason = reason;
self.status = 'rejected';
while (self.rejectCache.length) {
const fn = self.rejectCache.shift();
fn(self.reason)
}
}
}
setTimeout(run);
}
cb(this.resolve, this.reject)
}
_Promise.prototype.then = function (onFulfilled, onRejected) {
let self = this;
// 特性3 如果 `promise` / `then` 传入的参数不是函数,将会透传。
if (typeof onFulfilled !== 'function') {
onFulfilled = function(value) {
return value;
}
}
if (typeof onRejected !== 'function') {
onRejected = function(reason) {
return reason;
}
}
// 特性1 返回一个 _promise
return new _Promise(function (resolve, reject) {
this.resolveFn = function (value) {
try {
const x = onFulfilled(value);
// 如果返回了一个promise,则等待 promise 状态变化,否则直接 resolve
// 因为 then 返回一个 promise,同步返回值需要一个确定的值,所以返回值不可以是 promise 类型的
// 特性4 `promise` 通过 `resolve` / `reject` 来更改状态,`then` 回调函数通过获得**同步返回值**来返回状态。
x instanceof _Promise ? x.then(resolve, reject) : resolve(x);
} catch (e) {
reject(e);
}
};
this.rejectFn = function (reason) {
try {
const x = onRejected(reason);
x instanceof _Promise ? x.then(resolve, reject) : resolve(x);
} catch (e) {
reject(e);
}
};
switch (self.status) {
case 'pending':
self.resolveCache.push(this.resolveFn);
self.rejectCache.push(this.rejectFn);
break;
case 'fulfilled':
this.resolveFn(self.value);
break;
case 'rejected':
this.rejectFn(self.reason);
break;
}
});
}
Promise 的特性全部有所体现。但距离 Promise 的完整实现还是有所欠缺的。很多方法都没实现,如: catch , finally , Promise.resolve() , Promise.all() 等。
小康版
function _Promise(cb) {
this.status = 'pending';
this.value = '';
this.reason = '';
// 缓存方法
this.resolveCache = [];
this.rejectCache = [];
let self = this;
const resolve = function (value) {
function run() {
// 特性2 `promise` 状态确定后,不可再变化。
if (self.status === 'pending') {
// 特性5 链式调用时,下一个 `then` 可获得上一个 `then` 的返回值。
self.value = value;
self.status = 'fulfilled';
while (self.resolveCache.length) {
const fn = self.resolveCache.shift();
fn(self.value);
}
}
}
// 特性6,7的体现,如不是这种方式(像 run()),将会连续执行这个promise的 then 注册的所有回调事件然后再到其他的 promise 事件
// 应该是微任务实现,这里用 setTimeout 模拟
setTimeout(run);
}
const reject = function (reason) {
function run() {
// 特性2 `promise` 状态确定后,不可再变化。
if (self.status === 'pending') {
self.reason = reason;
self.status = 'rejected';
while (self.rejectCache.length) {
const fn = self.rejectCache.shift();
fn(self.reason)
}
}
}
setTimeout(run);
}
cb(resolve, reject)
}
_Promise.prototype.then = function (onFulfilled, onRejected) {
let self = this;
// 特性3 如果 `promise` / `then` 传入的参数不是函数,将会透传。
if (typeof onFulfilled !== 'function') {
onFulfilled = function(value) {
return value;
}
}
if (typeof onRejected !== 'function') {
onRejected = function(reason) {
return reason;
}
}
// 特性1 返回一个 _promise
return new _Promise(function (resolve, reject) {
resolveFn = function (value) {
try {
const x = onFulfilled(value);
// 如果返回了一个promise,则等待 promise 状态变化,否则直接 resolve
// 因为 then 返回一个 promise,同步返回值需要一个确定的值,所以返回值不可以是 promise 类型的
// 特性4 `promise` 通过 `resolve` / `reject` 来更改状态,`then` 回调函数通过获得**同步返回值**来返回状态。
x instanceof _Promise ? x.then(resolve, reject) : resolve(x);
} catch (e) {
reject(e);
}
};
rejectFn = function (reason) {
try {
const x = onRejected(reason);
x instanceof _Promise ? x.then(resolve, reject) : resolve(x);
} catch (e) {
reject(e);
}
};
switch (self.status) {
case 'pending':
self.resolveCache.push(resolveFn);
self.rejectCache.push(rejectFn);
break;
case 'fulfilled':
resolveFn(self.value);
break;
case 'rejected':
rejectFn(self.reason);
break;
}
});
}
// 该方法是无论 promise resolve 还是 reject ,都会运行的
// 且不会影响 resolve 或 reject 的值
// 因为不知道 promise 最终状态是什么,所以没有参数
_Promise.prototype.finally = function(cb) {
try {
return this.then(
value => new _Promise(resolve => resolve(cb())).then((res) => {
if (res instanceof _Promise && res.status === 'rejected') {
return res;
}
return value;
}),
error => new _Promise(resolve => resolve(cb())).then((_err) => {
return _Promise.reject(_err ? _err : error);
})
)
} catch (e) {
return new _Promise.reject(e);
}
}
_Promise.prototype.catch = function (rejectFn) {
return this.then(undefined, rejectFn);
}
_Promise.resolve = function (value) {
return value instanceof _Promise ? value : new _Promise(resolve => resolve(value));
}
_Promise.reject = function (e) {
return e instanceof _Promise ? e : new _Promise((undefined, reject) => reject(e));
}
此处要注意,finally 有以下特性:
注意: 在
finally回调中throw(或返回被拒绝的promise)将以throw()指定的原因拒绝新的promise.
下面对照着代码说明一下:
_Promise.prototype.finally = function(cb) {
// 这里用上 try {...} catch 是为了收集 throw error
try {
return this.then(
value => new _Promise(resolve => resolve(cb())).then((res) => {
// 如果 cb 执行后的返回值是_Promise 的实例,且状态是 rejected 的
// 那就直接返回该实例
if (res instanceof _Promise && res.status === 'rejected') {
return res;
}
return value;
}),
error => new _Promise(resolve => resolve(cb())).then((_err) => {
// 如果 本来就是 reject 的话,返回 reject 结果
// 如果 cb 执行后,返回新的 rejected 值,那将会取代原先的 rejected 值
return _Promise.reject(_err ? _err : error);
})
)
} catch (e) {
// 如 catch 到 error 那就直接 reject
return new _Promise.reject(e);
}
}
下面来实战一下:
var p = _Promise.reject('rejected')
.finally(() => {
// 正常
// console.log('finally');
// error
// throw Error('这里只有返回被拒绝的 promise 或者 throw 一个错误,才会影响当前 finally 返回的新 promise 的决议');
// rejected
return _Promise.reject('这里只有返回被拒绝的 promise 或者 throw 一个错误,才会影响当前 finally 返回的新 promise 的决议');
})
.then(value => {
console.log('成功', value)
}, (err) => {
console.log('失败', err)
});
// result:
// 失败 这里只有返回被拒绝的 promise 或者 throw 一个错误,才会影响当前 finally 返回的新 promise 的决议
// 如无 finally ,result 将会是:
// 失败 rejected
// 所以在 finally 里,新的 rejected 返回值取代了原先的 rejected 返回值
再看一段代码:
var p = _Promise.resolve('ok')
.finally(() => {
// 正常
// console.log('finally');
// error
// throw Error('这里只有返回被拒绝的 promise 或者 throw 一个错误,才会影响当前 finally 返回的新 promise 的决议');
// rejected
return _Promise.reject('这里只有返回被拒绝的 promise 或者 throw 一个错误,才会影响当前 finally 返回的新 promise 的决议');
})
.then(value => {
console.log('成功', value)
}, (err) => {
console.log('失败', err)
});
// result:
// 失败 这里只有返回被拒绝的 promise 或者 throw 一个错误,才会影响当前 finally 返回的新 promise 的决议
// fulfilled 状态被更改为 rejected 了
throw error 也会正常处理(也就是结果跟 Promise 结果一致)。
至此,应无其他问题。
相对贫民版来说,这个版本的 Promise 内部的 resolve ,reject 等方法不再挂在 this 上了。而且增加了 catch , finally ,静态方法 resolve , reject。实现到这里,应该是可以实践上使用了。有空再补上 all , race 方法。