释放 Promise 的威力:探索 6 个静态方法及其源码实现
先说总结
Promise 有 6 个静态方法,如果研究过 Promise 源码再看这些方法的实现,会非常容易理解。戳这里可以看到我写的 Promise 源码实现研究:我把 JS Promise 翻了个底朝天,以后再也不怕 Promise 面试题了
这 6 个静态方法可以总结成 3 对:
all
与allSettled
- 都是拿所有promise
的结果,区别是某个promise
失败是否会终止流程race
与any
- 都是拿最先完成的promise
的结果,区别同上resolve
与reject
- 快速生成一个已经resolve
或reject
的promise
使用场景
all
所有 promises 全部成功才算成功,一个失败即返回失败的原因。 all 的作用我是这么记的:“all or nothing”,不成功便成仁。
// iterable 可以是任何值
// 返回: 一个 promise, result is the list of the results of the input,返回的 list 的顺序和 iterable 里的顺序一致
let promise = Promise.all(iterable);
使用场景:适合我们需要所有 promise
都成功的场景,比如并行下载多个文件,每个文件都需要成功下载:
let urls = [
"https://api.github.com/users/iliakan",
"https://api.github.com/users/remy",
"https://api.github.com/users/jeresig",
];
// fetch 返回一个 promise,所以 requests 是存储了 promise 的 list
let requests = urls.map((url) => fetch(url));
// fetch 所有 url,结果全部保存在 responses 中
Promise.all(requests).then((responses) =>
responses.forEach((response) =>
console.log(`${response.url}: ${response.status}`)
)
);
一个更复杂的业务场景,一次性调用三个接口,等拿到所有的结果后提取请求体:
let names = ["iliakan", "remy", "jeresig"];
let requests = names.map((name) =>
fetch(`https://api.github.com/users/${name}`)
);
Promise.all(requests)
.then((responses) => {
// responses 是 fetch 上面三个接口得到的所有结果,长这样 [responseA, responseB, responseC]
// 打印每个接口调用的 http 状态码
for (let response of responses) {
console.log(`${response.url}: ${response.status}`);
}
// 原封不动给到下游
return responses;
})
.then((responses) => {
// responses 长这样:[responseA, responseB, responseC]
return Promise.all(
// 把 responses 转成 a list of promises,用于提取请求体
responses.map((r) => {
// r.json() 返回一个 promise
return r.json();
})
);
})
.then((users) => {
// users 是从 response.json() 里提取的数据
users.forEach((user) => console.log(user.name));
});
如果任何一个 promise
失败,会立即调用 catch
回调函数,同时其他 promise
依旧在执行,只不过拿不到结果而已:
Promise.all([
new Promise((resolve, reject) =>
setTimeout(() => resolve("我是拿不到结果"), 1000)
),
new Promise((resolve, reject) =>
setTimeout(() => resolve("我也是拿不到结果"), 30000)
),
new Promise((resolve, reject) =>
setTimeout(() => reject(new Error("出错啦")), 2000)
),
]).catch((error) => {
// Error: "出错啦"。同时另外两个 promise 会继续运行。
console.log(error);
});
我们给到 all
的参数可以是任何类型的值。因为 promise
会进行值穿透(值穿透原理可以在这里看到:promise 源码分析)
Promise.all([1, 2, 3]).then(console.log); // 1, 2, 3
allSettled
settled
中文意思是安定,我理解为不论 promise
是成功还是失败都算 settled
,某个 promise 失败也不影响流程,这是 allSettled 和 all 唯一的区别。
使用场景:通过接口拿多条用户信息,某个接口调用失败也不影响主流程。
let urls = [
"https://api.github.com/users/iliakan",
"https://api.github.com/users/remy",
"https://no-such-url",
];
Promise.allSettled(urls.map((url) => fetch(url))).then((results) => {
// results is a list of promises
console.log(results);
});
// output
// [
// {status: 'fulfilled', value: ...response...},
// {status: 'fulfilled', value: ...response...},
// {status: 'rejected', reason: ...error object...}
// ]
allSettled
是比较新的方法,如果当前的浏览器不支持,我们可以利用 Promise.all
实现 allSettled
:
if (!Promise.allSettledMyOwn) {
const rejectHandler = (reason) => ({ status: "rejected", reason });
const resolveHandler = (value) => ({ status: "fulfilled", value });
Promise.allSettledMyOwn = function (promises) {
const convertedPromises = promises.map((p) =>
// 用 then 来兜住 error,抛出的 error 会被 rejectHandler 捕获,从而不终止 all 的流程
Promise.resolve(p).then(resolveHandler, rejectHandler)
);
return Promise.all(convertedPromises);
};
}
race
顾名思义,a list of promises 比赛,谁先 settled (成功失败都算 settled),就拿到谁的结果。
Promise.race([
new Promise((resolve, _) => setTimeout(() => resolve("我第二个完成"), 2000)),
new Promise((_, reject) =>
setTimeout(() => reject(new Error("我是个错误,我最先完成")), 1000)
),
new Promise((resolve, _) => setTimeout(() => resolve("我第三个完成"), 3000)),
])
.then((v) => {
console.log(v);
})
.catch((err) => {
// 会走 catch 回调函数
console.log(err); // Error: 我是个错误,我最先完成
});
any
如果中途有的 promise
失败了,不管它,返回最先成功的 promise,这也是 any 和 race 唯一的区别。如果都失败了,返回一个列表,存有所有 promises
的错误原因。
resolve 和 reject
快速生成一个已经 resolved
或 reject
的 promise
源码实现分析
all 和 allSettled
all
实现 all
的核心思路是返回一个新的 promise
并在这个 promise
里面遍历所有的 promises。
注意点
- 要考虑参数不是
Promise
类型的情况 - 为了能让返回值的顺序和给到的
promises array
的顺序一致,不能用for loop
的i
直接判断是否已经遍历完成(如果最后一个promise
最先完成会直接结束)
Promise.myAll = function (arr) {
// 获取总数
const total = arr.length;
// 已经处理过的 promise 数量
let count = 0;
// 初始化 list,用来存所有 promise 的结果
const results = new Array(total);
// 因为返回的是一个新 promise,所以 new promise 并在其 executor 中遍历所有 arr
return new Promise((resolve, reject) => {
for (let i = 0; i < total; i++) {
// arr[i] 不一定是 promise,所以统一用 Promise.resolve 包裹
// 如果 arr[i] 本身就是 promise,通过 Promise.resolve 源码可知,Promise.resolve 会直接返回这个 promise
Promise.resolve(arr[i])
.then((res) => {
// 可以看出返回的 result list 顺序和给的 promise list 一致
results[i] = res;
count++;
if (count === total) {
// 如果全部的 promises 都成功了,resolve 这个新的 promise,把结果传给下游
resolve(results);
}
})
.catch((err) => {
// 如果有一个 promise 失败了就会走这个回调,
// 可以看到,就算某个 promise 失败了,也不会停止其他 promise
reject(err); // reject 这个 promise,把结果传给下游
});
}
});
};
// ==== 用例 ====
const arr = [
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 2000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
// reject("nono");
}, 1000);
}),
];
Promise.myAll(arr).then(console.log, console.log);
// Promise.all(arr).then(console.log, console.log);
allSettled
allSettled
和 all
功能非常相似,所以实现也和相似:
Promise.myAllSettled = function (arr) {
const total = arr.length;
let count = 0;
const results = new Array(total);
return new Promise((resolve, _) => {
for (let i = 0; i < total; i++) {
Promise.resolve(arr[i])
.then((res) => {
results[i] = {
status: "fulfilled",
value: res,
};
count++;
if (count === total) {
resolve(results);
}
})
.catch((err) => {
// 唯一和 all 方法不同的地方,这里不结束 promise,而是继续遍历
// all 方法这里是 reject(err);
results[i] = {
status: "rejected",
reason: err,
};
count++;
if (count === total) {
resolve(results); // 把结果通过 resolve 传给下游
}
});
}
});
};
// ==== 用例 ====
const arr = [
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 2000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
// resolve(3);
reject("nono");
}, 1000);
}),
];
// Promise.allSettled(arr).then(console.log, console.log);
Promise.myAllSettled(arr).then(console.log, console.log);
race 和 any
race
Promise.myRace = function (arr) {
// 返回新的 promise,把结果通过链式调用传给下游
return new Promise((resolve, reject) => {
arr.forEach((item) => {
// item 可能不是 promise,用 Promise.resolve 包裹
Promise.resolve(item)
.then((value) => {
// 如果 item promise 成功,会走这个回调
// 立即结束 promise
resolve(value);
})
.catch((err) => {
// 如果 item promise 失败,会走这个回调
// 立即结束 promise
reject(err);
});
});
});
};
// ==== 用例 ====
const arr = [
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('我晚一点结束');
}, 2000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('我最早结束');
}, 1000);
}),
];
// Promise.myRace(arr).then(console.log, console.log);
Promise.race(arr).then(console.log, console.log);
any
any
和 race
的功能非常相似,实现也很相似:
Promise.myAny = function (arr) {
return new Promise((resolve, reject) => {
arr.forEach((item) => {
Promise.resolve(item)
.then((value) => {
// 如果 item promise 成功,会走这个回调
// 立即结束 promise
resolve(value);
})
.catch((err) => {
// 不处理
});
});
});
};
// ==== 用例 ====
const arr = [
new Promise((resolve, reject) => {
setTimeout(() => {
resolve("成功");
}, 2000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
reject("nono");
}, 1000);
}),
];
Promise.myAny(arr).then(console.log, console.log);
// Promise.any(arr).then(console.log, console.log);
resolve 和 reject
resolve 和 reject 的实现很简单
/**
* 创建一个已经 resolve 的 promise 并返回
* @param value 可能是任意值 也可能是 promise
* @returns promise
*/
Promise.myResolve = function (value) {
if (value instanceof Promise) {
return value;
}
return new Promise((resolve, _) => {
resolve(value);
});
};
/**
* 和 Promise.resolve 一样,创建一个已经 reject 的 promise 并返回
*/
Promise.myReject = function (reason) {
return new Promise((_, reject) => reject(reason));
};
转载自:https://juejin.cn/post/7250383386183221285