概念
Promise 是一个 ECMAScript 6 提供的类,目的是更加优雅地书写复杂的异步任务,解决js中多个异步回调难以维护和控制的问题。
什么时候适合用 Promise 而不是传统回调函数?当需要多次顺序执行异步操作的时候,例如,如果想通过异步方法先后检测用户名和密码,需要先异步检测用户名,然后再异步检测密码的情况下就很适合 Promise。
如何使用 Promise
静态方法
方法 | 说明 |
---|---|
Promise.resolve(param) | 等同于 new Promise(function (resolve.,reject){resolve(param)}) |
Promise.reject(reason) | 等同于 new Promise(function (resolve.,reject){reject(reason)}) |
Promise.all([p1,…,pn]) | 输入一组promise返回一个新的promise,全部promise都是fulfilled结果才是fulfilled状态;如果有一个失败,结果promise就是失败 |
Promise.allSettled([p1,…,pn]) | 输入一组promise返回一个新的promise,所有的promise状态改变后,结果promise变成fulfilled |
Promise.race([p1,…,pn]) | 输入一组promise返回一个新的promise,结果promise的状态跟随第一个变化的promsie状态,最先返回promise是成功的,结果promise就是成功,否则就是失败 |
实例方法
方法 | 说明 |
---|---|
promise.then(onFulfilled,onRejected) | promise状态改变之后的回调,返回新的promise对想 |
Promise.catch(reason) | 同promise.then(null,onRejected),promise状态为rejected回调 |
Promise.finally( function(reason){} ) | 不管promise的状态如何都会执行 |
Promise 的应用与分析
信号灯
3秒后亮一次红灯,再过2秒亮一次绿灯,再过1秒亮一次黄灯,如此循环一直交替下去。
const LIGHTS = [
{
color: 'red',
time: 3000
},
{
color: 'green',
time: 2000
},
{
color: 'yellow',
time: 1000
}
];
function light({color, time}) {
return new Promise((resolve, reject) => {
try {
setTimeout(() => {
console.log(new Date().getSeconds(), color);
resolve();
}, time);
} catch(e) {
reject(e);
}
});
}
function main() {
let p = Promise.resolve();
LIGHTS.forEach(x => {
p = p.then(() => light(x));
});
p.then(() => main());
}
main();
复制代码
Promise.all 并发限制
要求每个时刻并发执行的promise数量是固定的,最终的执行结果还是保持与原来的Promise.all一致。
function multiRequest(urls = [], maxNum) {
// 请求总数量
const len = urls.length;
// 根据请求数量创建一个数组来保存请求的结果
const result = new Array(len).fill(false);
// 当前完成的数量
let count = 0;
return new Promise((resolve, reject) => {
// 请求maxNum个
while (count < maxNum) {
next();
}
function next() {
let current = count++;
// 处理边界条件
if (current >= len) {
// 请求全部完成就将promise置为成功状态, 然后将result作为promise值返回
!result.includes(false) && resolve(result);
return;
}
const url = urls[current];
console.log(`开始 ${current}`, new Date().toLocaleString());
fetch(url)
.then((res) => {
// 保存请求结果
result[current] = res;
console.log(`完成 ${current}`, new Date().toLocaleString());
// 请求没有全部完成, 就递归
if (current < len) {
next();
}
})
.catch((err) => {
console.log(`结束 ${current}`, new Date().toLocaleString());
result[current] = err;
// 请求没有全部完成, 就递归
if (current < len) {
next();
}
});
}
});
}
复制代码
Promise 执行顺序
/* Promise构造函数的参数是一个执行器,是同步的;
构造完立即注册then函数,等同步代码执行完毕后,执行then函数。*/
new Promise((resolve, reject) => {
console.log("1"); // 1. Promise构造函数接受的参数是一个需要立即执行的函数, 是一个同步任务
resolve();
})
.then(() => { // 2. 注册then方法,把它加到微任务队列
// 3. 没有同步代码,开始执行该微任务
console.log("2");
new Promise((resolve, reject) => { // 4. 继续执行Promise构造函数
console.log("3");
resolve();
})
.then(() => { // 5. 注册其then方法,将其加到微任务队列
console.log("4"); // 7. 执行
})
.then(() => { // 8. 注册
console.log("5"); // 10. 执行
});
})
.then(() => { // 6. 没有同步代码,第一个then执行完毕,继续注册外层 Promise 的第二个 then
console.log("6"); // 9. 执行
});
// 输出: 1 2 3 4 6 5
复制代码
new Promise((resolve, reject) => {
console.log("1"); // 1. 构造函数的参数,先执行
resolve();
})
.then(() => { // 2. 注册第一个then
console.log("2"); // 3. 执行第一个 then
// 看到return ,需要将表达式执行完毕,才能执行外层第二个then
return new Promise((resolve, reject) => {
console.log("3"); // 4. 构造函数执行
resolve();
})
.then(() => { // 5. 注册
console.log("4"); // 6. 执行
})
.then(() => { // 7. 注册
console.log("5"); // 8. 执行
});
})
.then(() => { // 9. 注册
console.log("6"); // 10. 执行
});
// 输出: 1 2 3 4 6 5
复制代码
手写一个 Promise
Promise A+ 规范
我们想要手写一个 Promise,就要遵循 Promise/A+ 规范。
Promise/A+ 规范详细阐述了如何实现一个符合标准的 Promise 类库。
- Promise 状态
Promise 的三个状态分别是 pending、fulfilled 和 rejected。
- pending: 待定,Promise 的初始状态。在此状态下可以落定 (settled) 为 fulfilled 或 rejected 状态。
- fulfilled: 兑现(解决),表示执行成功。Promise 被 resolve 后的状态,状态不可再改变,且有一个私有的值 value。
- rejected: 拒绝,表示执行失败。Promise 被 reject 后的状态,状态不可再改变,且有一个私有的原因 reason。
复制代码
注意:value 和 reason 也是不可变的,它们包含原始值或对象的不可修改的引用,默认值为 undefined。
- Then 方法
要求必须提供一个 then 方法来访问当前或最终的 value 或 reason。
promise.then(onFulfilled, onRejected)
1.then 方法接受两个函数作为参数,且参数可选。
2.如果可选参数不为函数时会被忽略。
3.两个函数都是异步执行,会放入事件队列等待下一轮 tick。
4.当调用 onFulfilled 函数时,会将当前 Promise 的 value 值作为参数传入。
5.当调用 onRejected 函数时,会将当前 Promise 的 reason 失败原因作为参数传入。
6.then 函数的返回值为 Promise。
7.then 可以被同一个 Promise 多次调用。
复制代码
- Promise 解决过程
Promise 的解决过程是一个抽象操作,接收一个 Promise 和一个值 x。
针对 x 的不同值处理以下几种情况:
1.x 等于 Promise
抛出 TypeError 错误,拒绝 Promise。
2.x 是 Promise 的实例
如果 x 处于待定状态,那么 Promise 继续等待直到 x 兑现或拒绝,否则根据 x 的状态兑现/拒绝 Promise。
3.x 是对象或函数
取出 x.then 并调用,调用时将 this 指向 x。将 then 回调函数中得到的结果 y 传入新的 Promise 解决过程中,递归调用。
如果执行报错,则将以对应的失败原因拒绝 Promise。
这种情况就是处理拥有 then() 函数的对象或函数,我们也叫它 thenable。
4.如果 x 不是对象或函数
以 x 作为值执行 Promise。
复制代码
完整代码
Follow 大佬文章,只能说理解和半记忆了代码,有待进一步实践和推敲。。。
好在自己梳理一遍后,promise 内部的原理更加清楚了,实现细节见文末参考文献1和2。
const FULFILLED = 'fulfilled';
const PENDING = 'pending';
const REJECTED = 'rejected';
class MyPromise {
constructor(excutor) {
// executor接受两个参数:resolve和reject方法,让用户控制promise是成功还是失败;调用resolve是成功,调用reject是失败。
try {
excutor(this.resolve, this.reject); // 新建 promise 对象时,执行函数立即执行,同步的
} catch (e) {
this.reject(e);
}
}
status = PENDING; // 异步任务的状态
result = undefined; // 成功的结果
reason = undefined; // 失败的原因
onFulfilledCallBackArr = [];
onRejectedCallBackArr = [];
// 代码中调用resolve或reject时,在全局下调用的,此时resolve/reject里面this是window,用箭头函数让this指向当前实例
// resolve 方法将状态改为 FULFILLED
resolve = (result) => {
if (this.status !== PENDING) return; // 状态只可以改变一次
this.status = FULFILLED;
this.result = result;
while (this.onFulfilledCallBackArr.length) {
this.onFulfilledCallBackArr.shift()(result);
}
}
// reject 方法将状态改为 REJECTED
reject = (reason) => {
if (this.status !== PENDING) return; // 状态只可以改变一次
this.status = REJECTED;
this.reason = reason;
while (this.onRejectedCallBackArr.length) {
this.onRejectedCallBackArr.shift()(reason);
}
}
// then 方法是核心:
// 1. 存储回调函数,支持多次调用then,支持异步任务完成后按注册顺序依次执行成功/失败的回调函数
// 2. 返回Promise,支持链式调用
// 3. 参数支持undefined
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => {
throw reason
};
// then 的链式调用,需要返回一个新的 promise
const newPromise = new MyPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
// 创建一个微任务等待 promise2 完成初始化
queueMicrotask(() => {
try {
// 获取成功回调函数的执行结果
const x = realOnFulfilled(this.result);
// 传入 resolvePromise 集中处理
resolvePromise(newPromise, x, resolve, reject);
} catch (error) {
reject(error)
}
})
};
const rejectedMicrotask = () => {
// 创建一个微任务等待 promise2 完成初始化
queueMicrotask(() => {
try {
// 调用失败回调,并且把原因返回
const x = realOnRejected(this.reason);
// 传入 resolvePromise 集中处理
resolvePromise(newPromise, x, resolve, reject);
} catch (error) {
reject(error)
}
})
};
switch (this.status) {
case PENDING: // 判断如果异步任务还没有完成,则保存成功和失败的回调,等任务完成后依次执行
this.onFulfilledCallBackArr.push(fulfilledMicrotask);
this.onRejectedCallBackArr.push(rejectedMicrotask);
break;
case FULFILLED:
fulfilledMicrotask();
break;
case REJECTED:
rejectedMicrotask();
break;
}
});
return newPromise;
}
catch (onRejected) {
this.then(undefined, onRejected);
}
static resolve(x) {
// 如果传入 MyPromise 就直接返回
if (parameter instanceof MyPromise) {
return parameter;
} else {
return new MyPromise((resolve, reject) => {
resolve(x)
});
}
}
static reject(x) {
return new MyPromise((resolve, reject) => {
reject(x);
});
}
// 全部成功
static all(promises) {
return new MyPromise((resolve, reject) => {
let values = [];
let count = 0;
promises.map((p, index) => {
if (p instanceof MyPromise) {
p.then(v => {
values[index] = v;
});
} else {
values[index] = p;
}
count++;
if (count === promises.length) {
resolve(value);
}
})
});
}
// 一个成功就成功
static race() {
return new MyPromise((resolve, reject) => {
promises.map(item => {
if (item instanceof Promise) {
item.then(
resolve, reject)
} else {
resolve(item)
}
})
});
}
// 内部方法
resolvePromise(p, x, resolve, reject) {
// 如果相等了,说明return的是自己,抛出类型错误并返回
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
}
if (x instanceof MyPromise) { // 判断x是不是 MyPromise 实例对象
// 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
// x.then(value => resolve(value), reason => reject(reason))
// 简化之后
x.then(resolve, reject);
} else {
// 普通值
resolve(x)
}
}
}
module.exports = MyPromise;
复制代码
参考
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END