p-reduce
// test.js
const inputs = [Promise.resolve(1), delay(50, { value: 6 }), 8];
async function main() {
const result = await pReduce(inputs, async (a, b) => a + b, 0);
console.log(result); // 输出结果:15
}
main();
// 看源码前,尝试实现
async function main2() {
const result = await inputs.reduce(async (a, b) => {
const x = await a;
const y = await b;
return x + y;
});
console.log("@@@@@", result);
}
// p-reduce源码
export default async function pReduce(iterable, reducer, initialValue) {
return new Promise((resolve, reject) => {
const iterator = iterable[Symbol.iterator]();
let index = 0;
const next = async total => {
const element = iterator.next();
if (element.done) {
resolve(total);
return;
}
try {
const [resolvedTotal, resolvedValue] = await Promise.all([total, element.value]);
next(reducer(resolvedTotal, resolvedValue, index++));
} catch (error) {
reject(error);
}
};
next(initialValue);
});
}
学到的东西: 利用 await Promise.all 来批量解构多个值并且还能保证顺序
// 看源码后,尝试实现
async function main2() {
const result = await inputs.reduce(async (a, b) => {
const [x, y] = await Promise.all([a, b]);
return x + y;
});
console.log("@@@@@", result);
}
p-map
实现并发限制, 类似于 Promise.all
export default async function pMap(
iterable,
mapper,
{ concurrency = Number.POSITIVE_INFINITY, stopOnError = true } = {}
) {
return new Promise((resolve, reject) => {
if (typeof mapper !== "function") {
throw new TypeError("Mapper function is required");
}
if (
!(
(Number.isSafeInteger(concurrency) ||
concurrency === Number.POSITIVE_INFINITY) &&
concurrency >= 1
)
) {
throw new TypeError(
`Expected \`concurrency\` to be an integer from 1 and up or \`Infinity\`, got \`${concurrency}\` (${typeof concurrency})`
);
}
const result = [];
const errors = [];
const skippedIndexes = [];
const iterator = iterable[Symbol.iterator](); // 执行这个函数,就会返回一个遍历器
let isRejected = false;
let isIterableDone = false;
let resolvingCount = 0;
let currentIndex = 0;
const next = () => {
if (isRejected) {
return;
}
// 每次执行next,就让迭代器指针右移
const nextItem = iterator.next();
// 因为currentIndex是可变的,且currentIndex用于async内部 所以需要衍生一个局部变量index
const index = currentIndex;
currentIndex++;
// 执行resolvingCount--; next()前未做判断, 所以每个并发分支的屁股上都有一次额外的next执行,
// 所以迭代完成以后,还会进入next n次( n= concurrency ), 每进入一次,说明有一个promise已完成
if (nextItem.done) {
isIterableDone = true;
// 只有所有的分支都完成,才真正的resolve,
// 有一个promise已完成,但并不知道其他分支上的promise情况
// 所以用resolvingCount 判断一下
if (resolvingCount === 0) {
if (!stopOnError && errors.length > 0) {
reject(new AggregateError(errors));
} else {
for (const skippedIndex of skippedIndexes) {
result.splice(skippedIndex, 1);
}
resolve(result);
}
}
return;
}
resolvingCount++;
// 同步函数中需要做异步时, 使用一个匿名自执行函数来包裹, 但也无法让next变成同步,不了解意义在哪里
// next 直接声明为 async next 也可以的,代码量还更少
(async () => {
try {
const element = await nextItem.value;
if (isRejected) {
return;
}
const value = await mapper(element, index);
if (value === pMapSkip) {
skippedIndexes.push(index);
} else {
result[index] = value;
}
resolvingCount--;
next();
} catch (error) {
if (stopOnError) {
isRejected = true;
reject(error);
} else {
errors.push(error);
resolvingCount--;
next();
}
}
})();
};
for (let index = 0; index < concurrency; index++) {
next();
// if(inputs长度 < 并发限制数)
if (isIterableDone) {
break;
}
}
});
}
export const pMapSkip = Symbol("skip");
学到的东西:循环中修改外部变量的方式来获取结果, 但有个缺点
- 在判断resolve的时机时, 多个分支共用一个全局的
resolvingCount和isIterableDone, 导致每个分支具体的细节被忽略, 如果换成resolvingCounts和isIterableDones的话,去跟踪每个分支的执行情况, 我觉得逻辑会更加可靠 - 使用了
iterable[Symbol.iterator],按理说可以手动控制每一个分支上的进度,在上一个promise完成以后,再.next(),这样就不会有额外的next执行, 也就不会有缺点一的问题出现