promise-fun 源码学习

419 阅读2分钟

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");

学到的东西:循环中修改外部变量的方式来获取结果, 但有个缺点

  1. 在判断resolve的时机时, 多个分支共用一个全局的 resolvingCountisIterableDone , 导致每个分支具体的细节被忽略, 如果换成 resolvingCountsisIterableDones 的话,去跟踪每个分支的执行情况, 我觉得逻辑会更加可靠
  2. 使用了 iterable[Symbol.iterator] ,按理说可以手动控制每一个分支上的进度,在上一个promise 完成以后,再.next() ,这样就不会有额外的 next 执行, 也就不会有缺点一的问题出现