Promise
Promise 是什么?
Promise
是异步编程的一种解决方案, 简单说就是一个容器,里面保存着一个尚未完成且预计在未来完成的异步操作
使用 new Promise
创建一个 Promise
对象, 用于表示一个异步操作的最终完成 (或失败)及其结果值, 有三种状态:
pending
(进行中)
fulfilled
(已完成)
rejected
(已失败)
pending
状态可以通过 resolve
异步操作转为 fulfilled
状态, 或者通过 reject
异步操作转为 rejected
状态
fulfilled
和 rejected
为最终态, 状态一旦改变就不会再变
优点
- 摆脱了回调地狱, 可以进行
.then
的链式调用, 让异步操作变得更加同步化, 流程更加清晰规范, 提高了代码的可维护性和可读性
缺点
- 一旦执行无法取消
- 无法跟踪进度
Promise/A+ 规范解读
术语
promise
是一个有then
方法的对象或者是函数,行为遵循本规范thenable
是一个有then
方法的对象或者是函数value
是promise
状态成功时的值,也就是resolve
的参数, 包括各种数据类型, 也包括undefined/thenable
或者是promise
reason
是promise
状态失败时的值, 也就是reject
的参数, 表示拒绝的原因exception
是一个使用throw
抛出的异常值
Promise Status
promise
应该有三种状态. 要注意他们之间的流转关系.
-
pending
1.1 初始的状态, 可改变.
1.2 一个promise
在resolve
或者reject
前都处于这个状态。
1.3 可以通过resolve
->fulfilled
状态;
1.4 可以通过reject
->rejected
状态; -
fulfilled
2.1 最终态, 不可变.
2.2 一个promise
被resolve
后会变成这个状态.
2.3 必须拥有一个value
值 -
rejected
3.1 最终态, 不可变.
3.2 一个promise
被reject
后会变成这个状态
3.3 必须拥有一个reason
Tips: 总结一下, 就是 promise
的状态流转是这样的
pending
-> resolve(value)
-> fulfilled
pending
-> reject(reason)
-> rejected
then
promise 应该提供一个 then 方法, 用来访问最终的结果, 无论是 value 还是 reason.
promise.then(onFulfilled, onRejected);
复制代码
-
参数要求
1.1 onFulfilled 必须是函数类型, 如果不是函数, 应该被忽略.
1.2 onRejected 必须是函数类型, 如果不是函数, 应该被忽略. -
onFulfilled 特性
2.1 在 promise 变成 fulfilled 时,应该调用 onFulfilled, 参数是 value
2.2 在 promise 变成 fulfilled 之前, 不应该被调用.
2.3 只能被调用一次(所以在实现的时候需要一个变量来限制执行次数) -
onRejected 特性
3.1 在 promise 变成 rejected 时,应该调用 onRejected, 参数是 reason
3.2 在 promise 变成 rejected 之前, 不应该被调用.
3.3 只能被调用一次(所以在实现的时候需要一个变量来限制执行次数) -
onFulfilled 和 onRejected 应该是微任务
这里用 queueMicrotask 来实现微任务的调用.
-
then 方法可以被调用多次
5.1 promise 状态变成 fulfilled 后,所有的 onFulfilled 回调都需要按照 then 的顺序执行, 也就是按照注册顺序执行(所以在实现的时候需要一个数组来存放多个 onFulfilled 的回调)
5.2 promise 状态变成 rejected 后,所有的 onRejected 回调都需要按照 then 的顺序执行, 也就是按照注册顺序执行(所以在实现的时候需要一个数组来存放多个 onRejected 的回调) -
返回值
then 应该返回一个 promise
promise2 = promise1.then(onFulfilled, onRejected); 复制代码
6.1 onFulfilled 或 onRejected 执行的结果为 x, 调用 resolvePromise( 这里大家可能难以理解, 可以先保留疑问, 下面详细讲一下 resolvePromise 是什么东西 )
6.2 如果 onFulfilled 或者 onRejected 执行时抛出异常 e, promise2 需要被 reject
6.3 如果 onFulfilled 不是一个函数, promise2 以 promise1 的 value 触发 fulfilled
6.4 如果 onRejected 不是一个函数, promise2 以 promise1 的 reason 触发 rejected -
resolvePromise
resolvePromise(promise2, x, resolve, reject); 复制代码
7.1 如果 promise2 和 x 相等,那么 reject TypeError
7.2 如果 x 是一个 promsie
如果 x 是 pending 态,那么 promise 必须要在 pending,直到 x 变成 fulfilled or rejected.
如果 x 被 fulfilled, fulfill promise with the same value.
如果 x 被 rejected, reject promise with the same reason.
7.3 如果 x 是一个 object 或者 是一个 function
let then = x.then.
如果 x.then 这步出错,那么 reject promise with e as the reason.
如果 then 是一个函数,then.call(x, resolvePromiseFn, rejectPromise)
resolvePromiseFn 的 入参是 y, 执行 resolvePromise(promise2, y, resolve, reject);
rejectPromise 的 入参是 r, reject promise with r.
如果 resolvePromise 和 rejectPromise 都调用了,那么第一个调用优先,后面的调用忽略。
如果调用 then 抛出异常 e
如果 resolvePromise 或 rejectPromise 已经被调用,那么忽略
则,reject promise with e as the reason
如果 then 不是一个 function. fulfill promise with x.
实现一个 Promise
一步步实现
平常用 promise
的时候, 是通过 new
关键字来 new Promise()
, 那就用 class
来实现一下吧.
class YuPromise {
constructor() {}
}
复制代码
定义三种状态类型
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
复制代码
设置初始状态
class YuPromise {
constructor() {
// 初始状态为pending
this.status = PENDING;
this.value = null;
this.reason = null;
}
}
复制代码
resolve
和 reject
方法
- 根据刚才的规范, 这两个方法是要更改 status 的, 从 pending 改到 fulfilled/rejected.
- 注意两个函数的入参分别是 value 和 reason.
class YuPromise {
constructor() {
// 初始状态为pending
this.status = PENDING;
this.value = null;
this.reason = null;
}
resolve(value) {
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED;
}
}
reject(reason) {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
}
}
}
复制代码
加一下 promise 入参
- 入参是一个函数, 函数接收 resolve 和 reject 两个参数.
- 注意在初始化 promise 的时候, 就要执行这个函数, 并且有任何报错都要通过 reject 抛出去
class YuPromise {
constructor(fn) {
// 初始状态为pending
this.status = PENDING;
this.value = null;
this.reason = null;
try {
// 绑定this, 使用当前promise实例上的 resolve 和 reject
fn(this.resolve.bind(this), this.reject.bind(this));
} catch (e) {
this.reject(e);
}
}
resolve(value) {
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED;
}
}
reject(reason) {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
}
}
}
复制代码
接下来来实现一下关键的 then 方法
- then 接收两个参数, onFulfilled 和 onRejected
then(onFulfilled, onRejected) {}
复制代码
- 检查并处理参数, 之前提到的如果不是 function, 就忽略. 这个忽略指的是原样返回 value 或者 reason.
isFunction(param) {
return typeof param === 'function';
}
then(onFulfilled, onRejected) {
const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) => {
return value
}
const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) => {
throw reason;
};
}
复制代码
- .then 的返回值整体是一个 promise, 用 promise 来包裹一下.
then(onFulfilled, onRejected) {
const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) => {
return value
}
const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) => {
throw reason;
};
const promise2 = new YuPromise((resolve, reject) => {})
return promise2
}
复制代码
- 根据当前 promise 的状态, 调用不同的函数
then(onFulfilled, onRejected) {
const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) => {
return value
}
const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) => {
throw reason;
};
const promise2 = new YuPromise((resolve, reject) => {
switch (this.status) {
// 这样写是在 then 函数调用瞬间会执行的, 主要是有一些直接决议的场景, 比如resolve(1)
case FULFILLED: {
realOnFulfilled()
break;
}
case REJECTED: {
realOnRejected()
break;
}
}
})
return promise2
}
复制代码
- 这样写, 是在 then 函数被调用的瞬间就会执行. 那这时候如果 status 还没变成 fulfilled 或者 rejected 怎么办, 很有可能还是 pending 的. 所以我们需要一个状态的监听机制, 当状态变成 fulfilled 或者 rejected 后, 再去执行 callback.
-
那么我们首先要拿到所有的 callback, 然后才能在某个时机去执行他. 新建两个数组, 来分别存储成功和失败的回调, 调用 then 的时候, 如果还是 pending 就存入数组.
// 使用数组, 主要是可以在一个 promise 实例上 .then 多次 this.fulfilledCallbackList = []; this.rejectedCallbackList = []; then(onFulfilled, onRejected) { const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) => { return value } const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) => { throw reason; }; const promise2 = new YuPromise((resolve, reject) => { switch (this.status) { case FULFILLED: { realOnFulfilled() break; } case REJECTED: { realOnRejected() break; } case PENDING: { this.fulfilledCallbackList.push(realOnFulfilled) this.rejectedCallbackList.push(realOnRejected) } } }) return promise2 } 复制代码
在 status 发生变化的时候, 就执行所有的回调. 这里用一下 es6 的 getter 和 setter. 这样更符合语义, 当 status 改变时, 去做什么事情. (当然也可以顺序执行, 在给 status 赋值后, 下面再加一行 forEach)
// 防止 getter 时套娃
this._status = PENDING;
get status() {
return this._status;
}
set status(newStatus) {
this._status = newStatus;
switch (newStatus) {
case FULFILLED: {
this.fulfilledCallbackList.forEach(callback => {
callback(this.value);
});
break;
}
case REJECTED: {
this.rejectedCallbackList.forEach(callback => {
callback(this.reason);
});
break;
}
}
}
复制代码
then 的返回值
上面只是简单说了下, then 的返回值是一个 Promise, 那么接下来具体讲一下返回 promise 的 value 和 reason 是什么.
- 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e。(这样的话, 我们就需要手动 catch 代码,遇到报错就 reject)
then(onFulfilled, onRejected) {
const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) => {
return value
}
const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) => {
throw reason;
};
const promise2 = new YuPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
try {
realOnFulfilled(this.value);
} catch (e) {
reject(e)
}
};
const rejectedMicrotask = () => {
try {
realOnRejected(this.reason);
} catch (e) {
reject(e);
}
}
switch (this.status) {
case FULFILLED: {
fulfilledMicrotask()
break;
}
case REJECTED: {
rejectedMicrotask()
break;
}
case PENDING: {
this.fulfilledCallbackList.push(realOnFulfilled)
this.rejectedCallbackList.push(realOnRejected)
}
}
})
return promise2
}
复制代码
-
如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
-
如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因。
需要注意的是,如果 promise1 的 onRejected 执行成功了,promise2 应该被 resolve
这里咱们其实已经在参数检查的时候做过了, 也就是这段代码
const realOnFulfilled = this.isFunction(onFulfilled)
? onFulfilled
: (value) => {
return value;
};
const realOnRejected = this.isFunction(onRejected)
? onRejected
: (reason) => {
throw reason;
};
复制代码
- 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行 resolvePromise 方法
then(onFulfilled, onRejected) {
const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) => {
return value
}
const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) => {
throw reason;
};
const promise2 = new YuPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
try {
realOnFulfilled(this.value);
} catch (e) {
reject(e)
}
};
const rejectedMicrotask = () => {
try {
realOnRejected(this.reason);
} catch (e) {
reject(e);
}
}
switch (this.status) {
case FULFILLED: {
fulfilledMicrotask()
break;
}
case REJECTED: {
rejectedMicrotask()
break;
}
case PENDING: {
this.fulfilledCallbackList.push(realOnFulfilled)
this.rejectedCallbackList.push(realOnRejected)
}
}
})
return promise2
}
复制代码
resolvePromise
resolvePromise(promise2, x, resolve, reject) {
// 如果 newPromise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 newPromise
// 这是为了防止死循环
if (promise2 === x) {
return reject(new TypeError('The promise and the return value are the same'));
}
if (x instanceof YuPromise) {
// 如果 x 为 Promise ,则使 newPromise 接受 x 的状态
// 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
queueMicrotask(() => {
x.then((y) => {
this.resolvePromise(promise2, y, resolve, reject);
}, reject);
})
} else if (typeof x === 'object' || this.isFunction(x)) {
// 如果 x 为对象或者函数
if (x === null) {
// null也会被判断为对象
return resolve(x);
}
let then = null;
try {
// 把 x.then 赋值给 then
then = x.then;
} catch (error) {
// 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
return reject(error);
}
// 如果 then 是函数
if (this.isFunction(then)) {
let called = false;
// 将 x 作为函数的作用域 this 调用
// 传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise
try {
then.call(
x,
// 如果 resolvePromise 以值 y 为参数被调用,则运行 resolvePromise
(y) => {
// 需要有一个变量called来保证只调用一次.
if (called) return;
called = true;
this.resolvePromise(promise2, y, resolve, reject);
},
// 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
(r) => {
if (called) return;
called = true;
reject(r);
});
} catch (error) {
// 如果调用 then 方法抛出了异常 e:
if (called) return;
// 否则以 e 为据因拒绝 promise
reject(error);
}
} else {
// 如果 then 不是函数,以 x 为参数执行 promise
resolve(x);
}
} else {
// 如果 x 不为对象或者函数,以 x 为参数执行 promise
resolve(x);
}
}
复制代码
onFulfilled 和 onRejected 是微任务
使用 queueMicrotask 包裹执行函数
const fulfilledMicrotask = () => {
queueMicrotask(() => {
try {
const x = realOnFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
};
const rejectedMicrotask = () => {
queueMicrotask(() => {
try {
const x = realOnRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
};
复制代码
简单写点代码测试一下
const test = new YuPromise((resolve, reject) => {
setTimeout(() => {
resolve(111);
}, 1000);
}).then(console.log);
console.log("case1", test);
setTimeout(() => {
console.log("case2", test);
}, 2000);
// 打印一下内容, 符合预期
// case1 YuPromise {
// _status: 'pending',
// value: null,
// reason: null,
// fullfilledList: [],
// rejectedList: [] }
// case2 YuPromise {
// _status: 'fullfilled',
// value: undefined,
// reason: null,
// fullfilledList: [],
// rejectedList: [] }
复制代码
为什么可以调用.then, 不可以调用.catch 呢? 因为我们并没有在类里面声明 catch 方法, 添加 catch 方法
catch (onRejected) {
return this.then(null, onRejected);
}
复制代码
promise.resolve
将现有对象转为 Promise 对象,如果 Promise.resolve 方法的参数,不是具有 then 方法的对象(又称 thenable 对象),则返回一个新的 Promise 对象,且它的状态为 fulfilled。
注意这是一个静态方法, 因为是通过 Promise.resolve 调用的, 而不是通过实例去调用的.
static resolve(value) {
if (value instanceof YuPromise) {
return value;
}
return new YuPromise((resolve) => {
resolve(value);
});
}
复制代码
promise.reject
返回一个新的 Promise 实例,该实例的状态为 rejected。Promise.reject 方法的参数 reason,会被传递给实例的回调函数。
static reject(reason) {
return new YuPromise((resolve, reject) => {
reject(reason);
});
}
复制代码
promise.race
const p = Promise.race([p1, p2, p3]);
该方法是将多个 Promise 实例,包装成一个新的 Promise 实例。
只要 p1、p2、p3 之中有一个实例率先改变状态,p 的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 p 的回调函数。
static race(promiseList) {
return new YuPromise((resolve, reject) => {
const length = promiseList.length;
if (length === 0) {
return resolve();
} else {
for (let i = 0; i < length; i++) {
YuPromise.resolve(promiseList[i]).then(
(value) => {
return resolve(value);
},
(reason) => {
return reject(reason);
});
}
}
});
}
复制代码
写段测试代码
const test = new YuPromise((resolve, reject) => {
setTimeout(() => {
resolve(111);
}, 1000);
});
const test2 = new YuPromise((resolve, reject) => {
setTimeout(() => {
resolve(222);
}, 2000);
});
const test3 = new YuPromise((resolve, reject) => {
setTimeout(() => {
resolve(333);
}, 3000);
});
YuPromise.race([test, test2, test3]).then(console.log); // 打印了 111, 符合预期
复制代码
完整代码
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
// queueMicrotask pollfill
if (typeof queueMicrotask !== "function") {
queueMicrotask = function (callback) {
Promise.resolve()
.then(callback)
.catch((e) => {
setTimeout(() => {
throw e;
});
});
};
}
class YuPromise {
constructor(fn) {
this.status = PENDING;
this._status = PENDING;
this.value = null;
this.reason = null;
this.fulfilledCallbackList = [];
this.rejectedCallbackList = [];
try {
fn(this.resolve.bind(this), this.reject.bind(this));
} catch (e) {
this.reject(e);
}
}
get status() {
return this._status;
}
set status(newStatus) {
this._status = newStatus;
switch (newStatus) {
case FULFILLED: {
this.fulfilledCallbackList.forEach((callback) => {
callback(this.value);
});
break;
}
case REJECTED: {
this.rejectedCallbackList.forEach((callback) => {
callback(this.reason);
});
break;
}
}
}
resolve(value) {
if (this.status !== PENDING) return;
this.value = value;
this.status = FULFILLED;
}
reject(reason) {
if (this.status !== PENDING) return;
this.reason = reason;
this.status = REJECTED;
}
then(onFulfilled, onRejected) {
const realOnFulfilled = this.isFunction(onFulfilled)
? onFulfilled
: (value) => {
return value;
};
const realOnRejected = this.isFunction(onRejected)
? onRejected
: (reason) => {
throw reason;
};
const promise2 = new YuPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
queueMicrotask(() => {
try {
const x = realOnFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
};
const rejectedMicrotask = () => {
queueMicrotask(() => {
try {
const x = realOnRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
};
switch (this.status) {
case FULFILLED: {
fulfilledMicrotask();
break;
}
case REJECTED: {
rejectedMicrotask();
break;
}
case PENDING: {
this.fulfilledCallbackList.push(fulfilledMicrotask);
this.rejectedCallbackList.push(rejectedMicrotask);
}
}
});
return promise2;
}
catch(onRejected) {
return this.then(null, onRejected);
}
isFunction(param) {
return typeof param === "function";
}
resolvePromise(promise2, x, resolve, reject) {
// 如果 newPromise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 newPromise
// 这是为了防止死循环
if (promise2 === x) {
return reject(
new TypeError("The promise and the return value are the same")
);
}
if (x instanceof YuPromise) {
// 如果 x 为 Promise ,则使 newPromise 接受 x 的状态
// 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
queueMicrotask(() => {
x.then((y) => {
this.resolvePromise(promise2, y, resolve, reject);
}, reject);
});
} else if (typeof x === "object" || this.isFunction(x)) {
// 如果 x 为对象或者函数
if (x === null) {
// null也会被判断为对象
return resolve(x);
}
let then = null;
try {
// 把 x.then 赋值给 then
then = x.then;
} catch (error) {
// 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
return reject(error);
}
// 如果 then 是函数
if (this.isFunction(then)) {
let called = false;
// 将 x 作为函数的作用域 this 调用
// 传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise
try {
then.call(
x,
// 如果 resolvePromise 以值 y 为参数被调用,则运行 resolvePromise
(y) => {
// 需要有一个变量called来保证只调用一次.
if (called) return;
called = true;
this.resolvePromise(promise2, y, resolve, reject);
},
// 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
(r) => {
if (called) return;
called = true;
reject(r);
}
);
} catch (error) {
// 如果调用 then 方法抛出了异常 e:
if (called) return;
// 否则以 e 为据因拒绝 promise
reject(error);
}
} else {
// 如果 then 不是函数,以 x 为参数执行 promise
resolve(x);
}
} else {
// 如果 x 不为对象或者函数,以 x 为参数执行 promise
resolve(x);
}
}
static resolve(value) {
if (value instanceof YuPromise) {
return value;
}
return new YuPromise((resolve) => {
resolve(value);
});
}
static reject(reason) {
return new YuPromise((resolve, reject) => {
reject(reason);
});
}
static race(promiseList) {
return new YuPromise((resolve, reject) => {
const length = promiseList.length;
if (length === 0) {
return resolve();
} else {
for (let i = 0; i < length; i++) {
YuPromise.resolve(promiseList[i]).then(
(value) => {
return resolve(value);
},
(reason) => {
return reject(reason);
}
);
}
}
});
}
}
复制代码
实现 Promise 周边
封装一个简单的 xhr 请求
- 使用 cb 形式
function ajax(url, success, fail) {
const client = new XMLHttpRequest();
client.open('GET', url);
client.onreadystatechange = function () {
if(this.readystate !== 4) {
return;
}
if(this.status === 200) {
success(this.response);
}else {
fail(new Error(this.statusText));
}
}
client.send();
}
// 如果请求2依赖请求1的结果, 那就在请求1的回调中再实现请求2, 一层层下去, 形成回调地狱
ajax('/test.json', function (res) {
console.log('success', res);
}, function(statusText) {
console.log('error', statusText);
});
复制代码
- 使用 promise 进行封装(ps: 通常用 promise 封装呢会使用 Async 的词根, 跟 Node.js 学的)
function ajaxAsync(url) {
return new Promise((resolve, reject) => {
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = function () {
if (this.readystate !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
client.send();
});
}
ajaxAsync("/test.json")
.then((res) => {
console.log("success", res);
})
.catch((err) => {
console.log("error", err);
});
复制代码
更改为 promise 的步骤呢, 就是内部构造 promise 实例, 在原来执行回调函数的地方执行对应的更改 promise 状态的函数即可
实现一个 Promise.chain, 一条一条执行
实现思路:
- for of 配合 async await
- 数组的 reduce 方法(进行转换, 转换成 promise1.then(() => {return promise2}) 的形式)
// 题目
function promiseCreator1() {
return new Promise((resolve) => {
setTimeout(resolve, 1000);
});
}
function promiseCreator2() {
return new Promise((resolve) => {
setTimeout(resolve, 1000);
});
}
const promiseCreatorList = [promiseCreator1, promiseCreator2];
复制代码
for of 配合 async await
async function main() {
async function forOfLoop() {
for (const promiseInstance of promiseCreatorList) {
await promiseInstance();
}
}
await forOfLoop();
}
main();
复制代码
数组的 reduce 方法
// reduce 可以接收第二个参数, 其实我们可以给它一个空的 Promise 实例, 那这样在迭代过程中就可以保证都是 Promise 实例
const promiseChain = promiseCreatorList.reduce((prev, cur) => {
return prev.then(cur);
}, Promise.resolve());
// 最后一次返回的值肯定也是一个 Promise 实例
promiseChain.then(() => {
console.log("end");
});
复制代码
Promise.allSeltted
该 Promise.allSettled()方法返回一个在所有给定的 promise 都已经 fulfilled 或 rejected 后的 promise,并带有一个对象数组,每个对象表示对应的 promise 结果。
function PromiseAllSeltted(promiseArray) {
return new Promise(function (resolve, reject) {
if (!Array.isArray(promiseArray)) {
return reject(new TypeError("arguments muse be an array"));
}
let counter = 0;
let promiseNum = promiseArray.length;
let resolvedArray = [];
for (let i = 0; i < promiseNum; i++) {
Promise.resolve(promiseArray[i])
.then((value) => {
resolvedArray[i] = {
value,
status: "fulfilled",
};
})
.catch((reason) => {
resolvedArray[i] = {
reason,
status: "rejected",
};
})
.finally(() => {
counter++;
if (counter == promiseNum) {
resolve(resolvedArray);
}
});
}
});
}
// 测试
const pro1 = new Promise((res, rej) => {
setTimeout(() => {
res("1");
}, 1000);
});
const pro2 = new Promise((res, rej) => {
setTimeout(() => {
rej("2");
}, 2000);
});
const pro3 = new Promise((res, rej) => {
setTimeout(() => {
res("3");
}, 3000);
});
const proAll = PromiseAllSeltted([pro1, pro2, pro3])
.then((res) => console.log(res))
.catch((e) => {
console.log(e);
});
复制代码
手写一个 Promise.all
它接收一个数组, 数组里面是 Promise 或者是常量, 返回一个 Promise, 它会使用 Promise.resolve 对数组元素包裹一层, 把非 Promise 元素也转变为 Promise, 当数组中所有的 Promise 执行完了, 就会 resolve 掉, 当有一个 Promise 报错了, 就会 reject 掉
当一个 Promise 报错后, 其他 Promise 会执行吗?
会的, Promise 在实例化的时候就执行完了, .then 只是为了拿到结果
function PromiseAll(promiseArray) {
// 首先肯定是返回一个 promise
return new Promise((resolve, reject) => {
// 判断一下是否是数组
if (!Array.isArray(promiseArray)) {
return reject(new Error("传入的参数需要是一个数组"));
}
let resArr = [];
let count = 0; // 记录执行返回的结果数量, 因为 resArr[i], 可能会出现 undefined, 可以举例子说明, 比如数组一开始为空, 如果 resArr[10] = 1, 这个时候的 resArr.length = 11, js 会把空间留出来
const promiseLen = promiseArray.length;
// ps: 自己百度的, 如果是数组这种采用下标访问的, 使用 for, 效率已经够高了, 如果是链表结构的, 可以使用 forEach
for (let i = 0; i < promiseLen; i++) {
// Promise.resolve 包裹一层可以把一些常量也转为 promise
Promise.resolve(promiseArray[i])
.then((res) => {
// 这种方式不对, 忽略了 Promise.all 的特性, Promise.all 接收的数组元素是什么顺序, 返回的结果也是什么顺序, 但是使用 push 有可能某一个 promise 执行得更快, 走在了前面 push
// resArr.push(res);
resArr[i] = res;
count++;
// 千万记住不能放在 .then 外面, 因为放外面是同步执行的
if (count === promiseLen) {
resolve(resArr);
}
})
.catch((err) => reject(err));
}
});
}
复制代码
实现一个 cachePromise
所有的 Promise 在实例化的时候就已经执行了
这道题目的意义是, 比如要去调用一个接口, 这个请求调用的是一个常量, 可能很多页面都使用到了, 那如果没有全局的状态管理, 那每个页面都调用一遍, 执行一遍, 也会比较浪费服务器的性能
那这里使用装饰器来实现一遍
装饰器科普
target 是所属类的原型
name 是修饰的属性名称
descriptor 是修饰的属性描述符号, 如 writable 这些配置等等, value 是属性值, 参考 Object.defineProperty
ES6 中定义一个类的写法,其实只是一个语法糖,而实际上当我们给一个类添加一个属性的时候,会调用到 Object.defineProperty 这个方法,它会接受三个参数:target 、name 和 descriptor
// 属性描述符
let descriptor = {
value: function () {
console.log("meow ~");
},
enumerable: false,
configurable: true,
writable: true,
};
// 经过 readonly 装饰器修饰后的属性描述符
descriptor = readonly(Cat.prototype, "say", descriptor) || descriptor;
// ES6中 给类添加属性
Object.defineProperty(Cat.prototype, "say", descriptor);
复制代码
当装饰器作用于类本身的时候,我们操作的对象也是这个类本身,而当装饰器作用于类的某个具体的属性的时候,我们操作的对象既不是类本身,也不是类的属性,而是它的描述符(descriptor),而描述符里记录着我们对这个属性的全部信息,所以,我们可以对它自由的进行扩展和封装,最后达到的目的呢,就和之前说过的装饰器的作用是一样的。
当然,如果你喜欢的话,也可以直接在 target 上进行扩展和封装
cachePromise 实现
刷新肯定就没了, 那没什么办法, 装饰器是 es7 的, 如果不支持可能要安装一个 babel 插件
有缓存, 就要考虑时效, 没有任何一个缓存是永久有效的, 在这里在去标识一下过期时间啊
const cacheMap = new Map();
function enableCache(target, name, descriptor) {
const val = descriptor.value;
descriptor.value = async function (...args) {
const cacheKey = `${name}${JSON.stringify(args)}`;
if (!cacheMap.get(cacheKey)) {
const cacheValue = Promise.resolve(val.apply(this, args)).catch((_) => {
cacheMap.set(cacheKey, null);
});
cacheMap.set(cacheKey, cacheValue);
}
return cacheMap.get(cacheKey);
};
return descriptor;
}
class PromiseClass {
@enableCache()
static async getInfo() {}
}
PromiseClass.getInfo.then(...).catch(...);
复制代码
Promise.race 的使用: 如果你的页面上有巨量的图片要展示, 那除了懒加载的形式, 还有什么形式可以来限制下同时加载的数量?(代码题, 写代码来实现并发的控制)
function limitLoad(urls, handler, limit) {
}
function loadImg(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, url.time);
});
}
const urls = [{ info: "info", time: 200 }...];
limitLoad(urls, loadImg, 3);
// 1, 2, 3, 4, 5, 6, 7
// 先请求 1, 2, 3, 如果其中有一个加载完了, 就从剩下的选一个接着请求
// 保证一个时间就是 3 个同时在请求
// 利用 Promise.race, 竞态, 只要有一个完成了, 会直接 resolve
function limitLoad(urls, handler, limit) {
// 不能对外部产生影响, 要进行拷贝
const sequeue = [].cancat(urls);
// 截取三个, 注意这里用的 splice(), 会改变原数组, 所以下面遍历的时候其实这里使用的三个是不存在的了
const promises = sequeue.splice(0, 3).map((url, index) => {
return handler(url).then(() => {
// 记录当前的 index, 用于下面填补
return index;
});
})
// 每次第一个完成了就会 resolve 掉
const p = Promise.race(promises);
// 这是一个同步 for 循环, 开启 .then().then()
for(let i = 0, len= sequeue.length; i < len; i ++) {
p = p.then((res) => {
promises[res] = handler(sequeue[i]).then(() => {
return res;
})
return Promise.race(promises);
})
}
}
// 思路:
// 首先截取三个去发送请求, 这三个请求就发出去了, 然后当有一个请求完成时, 就 resolve 了
// 然后在当前 resolve 的位置重新发出一个请求, 再开启一个竞态
// 这个遍历的话其实是链式调用, 会 .then().then().then(), 都是根据最先请求完成的位置开启的, 完成开启下一个
// 使用 Promise.race() 来实现并行, Promise 会在初始化的时候就执行了, 只不过是整个 Promise 会返回一个结果, Promise 数组里面的 Promise 还是会进行执行的
复制代码
看题说话
为什么 promise resolve 了一个 value, 最后输出的 value 值却是 undefined
const test = new YuPromise((resolve, reject) => {
setTimeout(() => {
resolve(111);
}, 1000);
}).then((value) => {
console.log("then");
});
setTimeout(() => {
console.log(test);
}, 3000);
复制代码
因为现在这种写法, 相当于在.then 里 return undefined, 所以最后的 value 是 undefined.
如果显式 return 一个值, 就不是 undefined 了;比如 return value.
.then 返回的是一个新 Promise, 那么原来 promise 实现的时候, 用数组来存回调函数有什么意义?
这个问题提出的时候, 应该是有一个假定条件, 就是链式调用的时候.
这个时候, 每一个.then 返回的都是一个新 promise, 所以每次回调数组 fulfilledCallbackList 都是空数组.
针对这种情况, 确实用数组来存储回调没意义, 完全可以就用一个变量来存储。
const test = new YuPromise((resolve, reject) => {
setTimeout(() => {
resolve(111);
}, 1000);
})
.then((value) => {})
.then(() => {});
复制代码
但是还有一种 promise 使用的方式, 这种情况下, promise 实例是同一个, 数组的存在就有了意义
const test = new YuPromise((resolve, reject) => {
setTimeout(() => {
resolve(111);
}, 1000);
});
test.then(() => {});
test.then(() => {});
test.then(() => {});
test.then(() => {});
复制代码
为什么我在 catch 的回调里, 打印 promise, 显示状态是 pending
const test = new YuPromise((resolve, reject) => {
setTimeout(() => {
reject(111);
}, 1000);
}).catch((reason) => {
console.log("报错" + reason);
console.log(test);
});
setTimeout(() => {
console.log(test);
}, 3000);
复制代码
- catch 函数会返回一个新的 promise, 而 test 就是这个新 promise
- catch 的回调里, 打印 promise 的时候, 整个回调还并没有执行完成(所以此时的状态是 pending), 只有当整个回调完成了, 才会更改状态
- catch 的回调函数, 如果成功执行完成了, 会改变这个新 Promise 的状态为 fulfilled
这段代码会打印什么?(注意 node 版本不同会有所区别)
const promise = new YuPromise((resolve) => {
setTimeout(() => {
resolve();
}, 1000);
}).then(() => {
console.log("case1", promise);
});
setTimeout(() => {
console.log("case2", promise);
}, 1000);
复制代码
Promise 实例化的时候是同步的, Promise 里的 setTimeout 先加入任务队列, 称为 s1 吧
再是全局的 setTimeout 加入任务队列, 称为 s2 吧
然后 s1 执行, resolve 是微任务, 加入微任务队列, 会在当前宏任务把控制权交还给浏览器前先清空微任务队列, 然后 case1 先打印, 状态为 pending
再执行 s2, 由于 then 中 相当于是返回了 undefined, s2 这里状态是 fullfilled
- 在浏览器中会是 case 1 -> case2, 没毛病
- 如果是在 node 环境运行, node 版本不同会有所差别, 如以下命令, node 版本为 10.16.3, case2 先执行了, node 版本为 14.17.1 时, 事件循环机制跟浏览器保持了一致, case1 先执行
// xxx@MacBook-Pro Promise % node -v
// v10.16.3
// xxx@MacBook-Pro Promise % node Promise.js
// case2
// case1
// xxx@MacBook-Pro Promise % nvm use 14.17.1
// Now using node v14.17.1 (npm v6.14.13)
// xxx@MacBook-Pro Promise % node -v
// v14.17.1
// xxx@MacBook-Pro Promise % node Promise.js
// case1
// case2
复制代码
其他问题
- 图片的懒加载是不是用的 Promise?
图片的懒加载通常是使用浏览器原生的 observer 来监听元素是否进入可视区, 替换元素的 src 来达到懒加载的目的
- getter 中没有什么逻辑, 为什么要写 getter 呢?
因为 getter 和 setter 是成对出现的
- 为什么要用一个私有变量呢?
因为如果 getter 中访问的是 this.status, 那就死循环, 套娃了, 使用一个私有变量_status
是为了防止套娃
- 为什么使用 queueMicrotask 包裹而不是 setTimeout ?
因为 setTimeout 是一个经典的宏任务
- 为什么 Promise.race 遍历, 传入 resolve, 不会多次执行?
因为当 resolve 或者 reject 异步操作执行了就会被锁.
- 为什么用数组来存?
因为数组可以存多个函数, 而且也满足顺序的条件
- then 每次 return 的都是一个新的 Promise, 新的 Promise 每次都会初始化数组为空的, 那你用数组存的意义在哪里? 为什么不用一个变量来存?
因为当前 promise 实例可以被多次调用, 不一定是链式调用
因为可以
promise.then(() => {});
promise.then(() => {});
promise.then(() => {});
promise.then(() => {});
promise.then(() => {});
复制代码
- resolvePromise 是为了什么?
为了解析 x 的值
- race 方法 list 中第一个 promise 被决议之后状态就变更了 list 剩下的 promise 也会执行 他们的结果就不了了之了么?
是的, 竞态, 谁跑的快就 resolve 谁, 或者 reject 谁, 其他的就陪跑
注意事项
-
try catch 只能捕获到同步抛出的错误, 不能捕获到异步任务抛出的错误, promise 的错误也不能捕获到, 除非使用了 async await
-
多个.then 链式调用, 最后用一个 .catch 捕获到的错误, 怎么区分是哪个 .then 抛出的错误, 所以其实也抛出一个问题, 如下写法其实是不严谨的, 像这个 catch 其实会捕获到两个 promise 抛出的错误, 所以如果需要区分的话, 可以在每一个 Promise 都 catch 一下.
-
每一个 Promise 的状态只取决于上次的 Promise 返回的状态, 如果上一个 Promise return 了一个值, 不是异常, 那 reject 就会终止掉
参考文献 & 待看文章
Promise – MDN
Using_promises – MDN
Promises/A+规范
Promise.allSettled() – MDN
装饰器 – JELLY
queueMicrotask – MDN
执行上下文 – MDN
协作异步 JavaScript – MDN
Promise 面试题 – 倔金
JavaScript 执行机制 – 倔金
Node 事件循环机制 – Node