Промис был сыгран с 48 «паттернами», а 10 общих модулей были глубоко проанализированы.

внешний интерфейс JavaScript Promise
Промис был сыгран с 48 «паттернами», а 10 общих модулей были глубоко проанализированы.

⚠️ Эта статья является первой подписанной статьей сообщества Nuggets, и ее перепечатка без разрешения запрещена.

В последнее время брат А Бао причесываетсяCLI(Интерфейс командной строки), связанный контент, на отличномLernaМне стало интересно, поэтому я начал «грызть» его исходники. При чтении проектов с открытым исходным кодом брат Абао привык читатьREADME.mdдокументация иpackage.jsonфайл, находясь вpackage.jsonдокументdependenciesВ поле брат А Бао видел многоp-* Пакеты зависимостей:

{
  "name": "lerna-monorepo",
  "version": "4.0.0-monorepo", 
  "dependencies": {
    "p-map": "^4.0.0",
    "p-map-series": "^2.1.0",
    "p-pipe": "^3.1.0",
    "p-queue": "^6.6.2",
    "p-reduce": "^2.1.0",
    "p-waterfall": "^2.1.1"
  }
}

Совет: если вы хотите знать, как прочитать проект Open Source Brother Bao, вы можете прочитатьИспользуя эти идеи и методы, я прочитал множество отличных проектов с открытым исходным кодом.Эта статья.

После этого Брат Абао нашел его, следуя за шипами.promise-fun (3.5K)этот проект. автор проектаsindresorhusЭто крупная корова с открытым исходным кодом, работающая полный рабочий день и принадлежащая Github.43.9Kподписчиков. В то же время он также поддерживает ряд отличных проектов с открытым исходным кодом, таких какawesome (167K),awesome-nodejs (42K),got (9.8K),ora (7.1K)а такжеscreenfull.js (6.1K)и другие проекты.

(Источник изображения --Res или Fox на GitHub.com/sin…

promise-funПроект записанsindresorhusнаписано48а такжеPromiseСвязанные модули, такие как те, что видели ранееp-map,p-map-series,p-pipeа такжеp-waterfallи другие модули. В этой статье Brother Abao покажет некоторые часто используемые модули, чтобы подробно проанализировать использование и конкретную реализацию кода каждого модуля.

Эти модули предоставляют множество полезных методов, которые могут помочь нам решить некоторые очень распространенные проблемы в нашей работе, такие как реализация управления параллелизмом, асинхронная обработка задач и т. д., особенно при работе с несколькими потоками управления, такими какseries,waterfall,all,raceа такжеforeverЖдать.

Друзья, вы готовы? Давайте начнемpromise-fun«Ознакомительный» тур проекта.

Инициализировать пример проекта

создать новыйlearn-promise-funпроект, а затем в корневом каталоге проекта выполнитьnpm init -yКоманда для инициализации проекта. При успешном выполнении команда будет сгенерирована в корневом каталоге проекта.package.jsonдокумент. из-заpromise-funМногие модули в проекте используют модуль ES. Чтобы убедиться, что последующий пример кода может нормально работать, нам нужноpackage.jsonфайл, добавитьtypeполе и установите его значение равным"module".

Из-за местногоNode.jsВерсияv12.16.2, чтобы запустить модуль модуля ES, также добавьте--experimental-modulesпараметры командной строки. И если вы не хотите видеть предупреждающее сообщение, вы также можете добавить--no-warningsпараметры командной строки. Кроме того, чтобы избежать необходимости вводить вышеуказанные параметры командной строки каждый раз при запуске примера кода, мы можемpackage.jsonизscriptsПоля определяют соответствующиеnpm scriptКоманда, как показано ниже:

{
  "name": "learn-promise-fun",
  "type": "module",
  "scripts": {
    "pall": "node --experimental-modules ./p-all/p-all.test.js",
    "pfilter": "node --experimental-modules ./p-filter/p-filter.test.js",
    "pforever": "node --experimental-modules ./p-forever/p-forever.test.js",
    "preduce": "node --experimental-modules ./p-reduce/p-reduce.test.js",
    ...
  },
}

После завершения инициализации проекта давайте сначала рассмотрим те, которые вы обычно используете больше.reduce,mapа такжеfilterОсобенности методов массива:

Совет: изображение выше через 👉carbon.now.sh/Генерация веб-страниц в Интернете.

Я верю, что все на картинкеArray.prototype.reduceметод не является незнакомым, этот метод используется для выполненияreducerфункция и объединяет результаты в одно возвращаемое значение. Соответствующий пример использования выглядит следующим образом:

const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator + currentValue;

// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer)); // 10

вreducerФункция принимает 4 параметра:

  • акк (аккумулятор): аккумулятор
  • cur(Current Value): текущее значение
  • idx (текущий индекс): текущий индекс
  • SRC (массив источника): исходный массив

Далее мы представимp-reduceмодуль, обеспечивающийArray.prototype.reduceметод, аналогичный функции.

p-reduce

Reduce a list of values using promises into a promise for a value

Res или Fox на GitHub.com/sin…

Инструкции по применению

p-reduceОн подходит для сценариев, в которых накопленное значение необходимо вычислять на основе асинхронных ресурсов. Модуль экспортирует значение по умолчаниюpReduceфункция со следующей сигнатурой:

pReduce(input, reducer, initialValue): Promise

  • input: Iterable<Promise|any>
  • reducer(previousValue, currentValue, index): Function
  • initialValue: unknown

пониматьpReduceПосле подписи функции посмотрим, как эта функция используется.

Пример использования

// p-reduce/p-reduce.test.js
import delay from "delay";
import pReduce from "p-reduce";

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.dir(result); // 输出结果:15
}

main();

В приведенном выше примере мы импортировалиdelayМодули экспортируются по умолчаниюdelayметод, который можно использовать для отсрочки объекта Promise на заданное время. То есть через заданное время состояние Promise станетresolved. по умолчанию,delayВнутри модуль сквознойsetTimeoutAPI для реализации функции задержки. примерdelay(50, { value: 6 })Указывает, что после задержки в 50 мс возвращаемое значение объекта Promise равно6. пока вmainВнутри функции мы используемpReduceфункция для вычисленияinputsНакопленное значение элементов массива. Когда приведенный выше код запустится успешно, командная строка выведет15.

Давайте проанализируемpReduceКак функция реализована внутри.

Анализ исходного кода

// https://github.com/sindresorhus/p-reduce/blob/main/index.js
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,
        ]);
        // 迭代下一项
        // reducer(previousValue, currentValue, index): Function
        next(reducer(resolvedTotal, resolvedValue, index++));
      } catch (error) {
        reject(error);
      }
    };

    // 使用初始值,开始迭代
    next(initialValue);
  });
}

В приведенном выше коде основным процессом является получениеiterableИтератор внутри объекта для непрерывной итерации. Кроме того, вpReduceфункция, используяPromise.allметод, который возвращает объект обещания, когда состояние всех входных объектов обещания сталоresolved, возвращаемый объект обещания вернет результат после разрешения каждого объекта обещания в виде массива. Когда состояние любого из входных объектов-обещаний становитсяrejected, возвращенный объект обещания отклонит соответствующее сообщение об ошибке.

Однако следует отметить, что,Promise.allСуществует проблема совместимости с методом, и конкретная совместимость показана на следующем рисунке:

(Источник изображения --потрите newser.com/?search=pro…

Может быть, некоторые друзьяPromise.allНе знаком, это относительно высокая частота рукописного заголовка. Итак, следующий мы приходим к простому рукописную версиюPromise.all:

Promise.all = function (iterators) {
  return new Promise((resolve, reject) => {
    if (!iterators || iterators.length === 0) {
      resolve([]);
    } else {
      let count = 0; // 计数器,用于判断所有任务是否执行完成
      let result = []; // 结果数组
      for (let i = 0; i < iterators.length; i++) {
        // 考虑到iterators[i]可能是普通对象,则统一包装为Promise对象
        Promise.resolve(iterators[i]).then(
          (data) => {
            result[i] = data; // 按顺序保存对应的结果
            // 当所有任务都执行完成后,再统一返回结果
            if (++count === iterators.length) {
              resolve(result);
            }
          },
          (err) => {
            reject(err); // 任何一个Promise对象执行失败,则调用reject()方法
            return;
          }
        );
      }
    }
  });
};

p-map

Map over promises concurrently

Res или Fox на GitHub.com/sin…

Инструкции по применению

p-mapХорошо работает для нескольких прогонов с разными входными даннымиpromise-returningилиasyncфункциональная сцена. с ранее описаннымPromise.allРазница в подходе заключается в том, что вы можете контролировать параллелизм, а также решать, останавливать ли итерацию при ошибке. Модуль экспортирует значение по умолчаниюpMapфункция со следующей сигнатурой:

pMap(input, mapper, options): Promise

  • input: Iterable<Promise | unknown>
  • mapper(element, index): Function
  • options: object
    • concurrency: number-- количество параллелизма, по умолчаниюInfinity, минимальное значение1;
    • stopOnError: boolean—— Следует ли завершать работу при возникновении исключения, значение по умолчанию равноtrue.

пониматьpMapПосле подписи функции посмотрим, как эта функция используется.

Пример использования

// p-map/p-map.test.js
import delay from "delay";
import pMap from "p-map";

const inputs = [200, 100, 50];
const mapper = (value) => delay(value, { value });

async function main() {
  console.time("start");
  const result = await pMap(inputs, mapper, { concurrency: 1 });
  console.dir(result); // 输出结果:[ 200, 100, 50 ]
  console.timeEnd("start");
}

main();

В приведенном выше примере мы также использовалиdelayмодуль экспортированdelayфункция для реализации функции задержки. Когда приведенный выше код будет успешно выполнен, командная строка выведет следующую информацию:

[ 200, 100, 50 ]
start: 368.708ms

ПокаconcurrencyИзмените значение свойства на2После этого снова выполните приведенный выше код. Затем командная строка выведет следующую информацию:

[ 200, 100, 50 ]
start: 210.322ms

Глядя на приведенные выше результаты вывода, мы видим, что количество параллелизма равно1, время работы программы превышает 350 мс. И если количество параллелизма2, несколько задач выполняются параллельно, поэтому программа выполняется чуть более 210 миллисекунд. ТакpMapКак функция реализует внутренний контроль параллелизма? Давайте проанализируемpMapИсходный код функции.

Анализ исходного кода

// https://github.com/sindresorhus/p-map/blob/main/index.js
import AggregateError from "aggregate-error";

export default async function pMap(
  iterable,
  mapper,
  { concurrency = Number.POSITIVE_INFINITY, stopOnError = true } = {}
) {
  return new Promise((resolve, reject) => {
    // 省略参数校验代码
    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;
      }

      const nextItem = iterator.next(); // 获取下一项
      const index = currentIndex; // 记录当前的索引值
      currentIndex++;

      if (nextItem.done) { // 判断迭代器是否迭代完成
        isIterableDone = true;

        // 判断是否所有的任务都已经完成了
        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++; // 正在处理的任务数加1

      (async () => {
        try {
          const element = await nextItem.value;

          if (isRejected) {
            return;
          }

          // 调用mapper函数,进行值进行处理
          const value = await mapper(element, index);
          // 处理跳过的情形,可以在mapper函数中返回pMapSkip,来跳过当前项
          // 比如在异常捕获的catch语句中,返回pMapSkip值
          if (value === pMapSkip) { // pMapSkip = Symbol("skip")
            skippedIndexes.push(index);
          } else {
            result[index] = value; // 把返回值按照索引进行保存
          }

          resolvingCount--;
          next(); // 迭代下一项
        } catch (error) {
          if (stopOnError) { // 出现异常时,是否终止,默认值为true
            isRejected = true;
            reject(error);
          } else {
            errors.push(error);
            resolvingCount--;
            next();
          }
        }
      })();
    };

    // 根据配置的concurrency值,并发执行任务
    for (let index = 0; index < concurrency; index++) {
      next();
      if (isIterableDone) {
        break;
      }
    }
  });
}

export const pMapSkip = Symbol("skip");

pMapЛогика обработки внутри функции достаточно понятна, а основная логика обработки инкапсулирована вnextв функции. вызовpMapПосле функции внутренняя будет настроена в соответствии сconcurrencyстоимость, одновременные вызовыnextфункция. пока вnextВнутри функции он пройдетasync/awaitконтролировать выполнение задания.

существуетpMapВ функции автор хитро спроектировалpMapSkip. когда мыmapperфункция возвращенаpMapSkipПосле этого значение соответствующего элемента индекса будет удалено из возвращаемого массива результатов. пониматьpMapSkipПосле роли возьмем простой пример:

import pMap, { pMapSkip } from "p-map";

const inputs = [200, pMapSkip, 50];
const mapper = async (value) => value;

async function main() {
  console.time("start");
  const result = await pMap(inputs, mapper, { concurrency: 2 });
  console.dir(result); // [ 200, 50 ]
  console.timeEnd("start");
}

main();

В приведенном выше коде нашinputsМассив содержитpMapSkipзначение при использованииpMapфункциональная параinputsПосле обработки массиваpMapSkipзначения будут отфильтрованы, поэтому в конечном итогеresultРезультат[200 , 50].

p-filter

Filter promises concurrently

Res или Fox на GitHub.com/sin…

Инструкции по применению

p-filterХорошо работает для нескольких прогонов с разными входными даннымиpromise-returningилиasyncфункцию и отфильтровать возвращаемые результаты. Модуль экспортирует значение по умолчаниюpFilterфункция со следующей сигнатурой:

pFilter(input, filterer, options): Promise

  • input: Iterable<Promise | any>
  • filterer(element, index): Function
  • options: object
    • concurrency: number-- количество параллелизма, по умолчаниюInfinity, минимальное значение1.

пониматьpFilterПосле подписи функции посмотрим, как эта функция используется.

Пример использования

// p-filter/p-filter.test.js
import pFilter from "p-filter";

const inputs = [Promise.resolve(1), 2, 3];
const filterer = (x) => x % 2;

async function main() {
  const result = await pFilter(inputs, filterer, { concurrency: 1 });
  console.dir(result); // 输出结果:[ 1, 3 ]
}

main();

В приведенном выше примере мы используемpFilterфункциональная пара содержитPromiseобъектinputsмассив, применяется(x) => x % 2фильтр. Когда приведенный выше код запустится успешно, командная строка выведет[1, 3].

Анализ исходного кода

// https://github.com/sindresorhus/p-filter/blob/main/index.js
const pMap = require('p-map');

const pFilter = async (iterable, filterer, options) => {
	const values = await pMap(
		iterable,
		(element, index) => Promise.all([filterer(element, index), element]),
		options
	);
	return values.filter(value => Boolean(value[0])).map(value => value[1]);
};

Как видно из приведенного выше кода, вpFilterВнутри функции мы используем ранее описанныйpMapа такжеPromise.allфункция. Чтобы понять приведенный выше код, нам также необходимо просмотретьpMapКлючевой код функции:

// https://github.com/sindresorhus/p-map/blob/main/index.js
export default async function pMap(
  iterable, mapper,
  { concurrency = Number.POSITIVE_INFINITY, stopOnError = true } = {}
) {
  const iterator = iterable[Symbol.iterator](); // 获取迭代器
  let currentIndex = 0; // 当前索引
  
  const next = () => {
    const nextItem = iterator.next(); // 获取下一项
    const index = currentIndex;
    currentIndex++;
    (async () => {
        try {
          // element => await Promise.resolve(1);
          const element = await nextItem.value;
          // mapper => (element, index) => Promise.all([filterer(element, index), element])
          const value = await mapper(element, index);
          if (value === pMapSkip) {
            skippedIndexes.push(index);
          } else {
            result[index] = value; // 把返回值按照索引进行保存
          }
          resolvingCount--;
          next(); // 迭代下一项
        } catch (error) {
          // 省略异常处理代码
        }
    })();
  }	
}

потому чтоpFilterиспользуется в функцииmapperфункция(element, index) => Promise.all([filterer(element, index), element]),такawait mapper(element, index)Возвращаемое значение выражения представляет собой массив. Элемент 1 массиваfiltererРезультат обработки фильтра, а вторым элементом массива является значение текущего элемента. Так зоветpMapПосле функции возвращаемое значение представляет собой двумерный массив. Так получаяpMapПосле возвращаемого значения функции для обработки возвращаемого значения используется следующий оператор:

values.filter(value => Boolean(value[0])).map(value => value[1])

В самом деле, для предыдущегоpFilterНапример, кромеinputsможет содержать объекты Promise, нашиfiltererФильтры также могут возвращать объекты Promise:

import pFilter from "p-filter";

const inputs = [Promise.resolve(1), 2, 3];
const filterer = (x) => Promise.resolve(x % 2);

async function main() {
  const result = await pFilter(inputs, filterer);
  console.dir(result); // [ 1, 3 ]
}

main();

После успешного выполнения приведенного выше кода вывод командной строки также[1, 3]. Хорошо, теперь мы рассмотрелиp-reduce,p-mapа такжеp-filter3 модуля. Продолжим знакомить с другим модулем -p-waterfall.

p-waterfall

Run promise-returning & async functions in series, each passing its result to the next

Res или Fox на GitHub.com/sin…

Инструкции по применению

p-waterfallПодходит для серийного исполненияpromise-returningилиasyncфункция и автоматически передает результат возврата предыдущей функции следующей функции. Модуль экспортирует значение по умолчаниюpWaterfallфункция со следующей сигнатурой:

pWaterfall(tasks, initialValue): Promise

  • tasks: Iterable<Function>
  • initialValue: unknown: будет использоваться как первая задачаpreviousValue

пониматьpWaterfallПосле подписи функции посмотрим, как эта функция используется.

Пример использования

// p-waterfall/p-waterfall.test.js
import pWaterfall from "p-waterfall";

const tasks = [
  async (val) => val + 1,
  (val) => val + 2,
  async (val) => val + 3,
];

async function main() {
  const result = await pWaterfall(tasks, 0);
  console.dir(result); // 输出结果:6
}

main();

В приведенном выше примере мы создали 3 задачи, а затем использовалиpWaterfallфункция для выполнения этих 3 задач. Когда приведенный выше код будет успешно выполнен, командная строка выведет6. Соответствующий поток выполнения показан на следующем рисунке:

Анализ исходного кода

// https://github.com/sindresorhus/p-waterfall/blob/main/index.js
import pReduce from 'p-reduce';

export default async function pWaterfall(iterable, initialValue) {
	return pReduce(iterable, (previousValue, function_) => function_(previousValue), initialValue);
}

существуетpWaterfallВнутри функции мы будем использовать введенный ранееpReduceфункция для реализацииwaterfallКонтроль над процессом. Аналогичным образом, чтобы понять поток внутреннего контроля, нам необходимо рассмотретьpReduceКонкретная реализация функции:

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 {
        // 首次调用next函数的状态:
        // resolvedTotal => 0
        // element.value => async (val) => val + 1
        const [resolvedTotal, resolvedValue] = await Promise.all([
          total,
          element.value,
        ]);
        // reducer => (previousValue, function_) => function_(previousValue)
        next(reducer(resolvedTotal, resolvedValue, index++));
      } catch (error) {
        reject(error);
      }
    };

    // 使用初始值,开始迭代
    next(initialValue);
  });
}

Теперь мы знаемpWaterfallФункция будет передавать выходные данные предыдущей задачи в качестве входных данных для следующей задачи. Но иногда при последовательном выполнении каждой задачи нас не волнует возвращаемое значение каждой задачи. В этом случае мы можем рассмотреть возможность использованияp-seriesпредоставляется модулемpSeriesфункция.

p-series

Run promise-returning & async functions in series

Res или Fox на GitHub.com/sin…

Инструкции по применению

p-seriesПодходит для серийного исполненияpromise-returningилиasyncфункциональная сцена.

Пример использования

// p-series/p-series.test.js
import pSeries from "p-series";

const tasks = [async () => 1 + 1, () => 2 + 2, async () => 3 + 3];

async function main() {
  const result = await pSeries(tasks);
  console.dir(result); // 输出结果:[2, 4, 6]
}

main();

В приведенном выше примере мы создали 3 задачи, а затем использовалиpSeriesфункция для выполнения этих 3 задач. Когда приведенный выше код будет успешно выполнен, командная строка выведет[ 2, 4, 6 ]. Соответствующий поток выполнения показан на следующем рисунке:

Анализ исходного кода

// https://github.com/sindresorhus/p-series/blob/main/index.js
export default async function pSeries(tasks) {
	for (const task of tasks) {
		if (typeof task !== 'function') {
			throw new TypeError(`Expected task to be a \`Function\`, received \`${typeof task}\``);
		}
	}

	const results = [];

	for (const task of tasks) {
		results.push(await task()); // eslint-disable-line no-await-in-loop
	}

	return results;
}

Как видно из приведенного выше кода, вpSeriesВнутри функции используетсяfor...ofзаявление иasync/awaitфункция для реализации последовательного управления потоком задач. Поэтому в реальных проектах можно легко реализовать последовательное управление потоком задач без использования этого модуля.

p-all

Run promise-returning & async functions concurrently with optional limited concurrency

Res или Fox на GitHub.com/sin…

Инструкции по применению

p-allПодходит для одновременного выполненияpromise-returningилиasyncфункциональная сцена. Этот модуль предоставляет функции иPromise.allAPI похож, главное отличие в том, что этот модуль позволяет ограничить количество одновременных задач. В ежедневном процессе разработки относительно распространенным сценарием является контроль количества одновременных HTTP-запросов.В настоящее время вы также можете рассмотреть возможность использованияasync-poolЭта библиотека решает проблему контроля параллелизма.Если вас интересует внутренняя реализация библиотеки, вы можете прочитатьКак реализовать контроль параллелизма в JavaScript?Эта статья.

Продолжаем знакомитьp-allмодуль, который экспортирует значение по умолчаниюpAllфункция со следующей сигнатурой:

pAll(tasks, options)

  • tasks: Iterable<Function>
  • options: object
    • concurrency: number-- количество параллелизма, по умолчаниюInfinity, минимальное значение1;
    • stopOnError: boolean—— Следует ли завершать работу при возникновении исключения, значение по умолчанию равноtrue.

Пример использования

// p-all/p-all.test.js
import delay from "delay";
import pAll from "p-all";

const inputs = [
  () => delay(200, { value: 1 }),
  async () => {
    await delay(100);
    return 2;
  },
  async () => 8,
];

async function main() {
  console.time("start");
  const result = await pAll(inputs, { concurrency: 1 });
  console.dir(result); // 输出结果:[ 1, 2, 8 ]
  console.timeEnd("start");
}

main();

В приведенном выше примере мы создали 3 асинхронных задачи, а затем передалиpAllфункция для выполнения созданной задачи. Когда приведенный выше код будет успешно выполнен, командная строка выведет следующую информацию:

[ 1, 2, 8 ]
start: 312.561ms

и когдаconcurrencyИзмените значение свойства на2После этого снова выполните приведенный выше код. Затем командная строка выведет следующую информацию:

[ 1, 2, 8 ]
start: 209.469ms

Видно, что количество одновременно1, время выполнения программы превышает 300 мс. И если количество параллелизма2, первые две задачи выполняются параллельно, поэтому время работы программы составляет всего более 200 миллисекунд.

Анализ исходного кода

// https://github.com/sindresorhus/p-all/blob/main/index.js
import pMap from 'p-map';

export default async function pAll(iterable, options) {
	return pMap(iterable, element => element(), options);
}

Очевидно,pAllВнутри функции черезp-mapпредоставляется модулемpMapфункция для реализации контроля параллелизма. если ты правpMapЕсли внутренняя реализация функции непонятна, вы можете вернуться и прочитать ее еще раз.p-mapсоответствующее содержимое модуля. Далее продолжим вводить еще один модуль -p-race.

p-race

A better Promise.race()

Res или Fox на GitHub.com/sin…

Инструкции по применению

p-raceЭтот мод исправляетPromise.race"Глупое" поведение API. При использовании пустого итерируемого объекта вызовитеPromise.raceAPI, вернет всегда вpendingОбещайте объекты для состояния, что может создать некоторые очень сложные для отладки проблемы. И еслиp-raceпредоставляется модулемpRaceКогда в функцию передается пустой итерируемый объект, функция немедленно выдаетRangeError: Expected the iterable to contain at least one itemинформация об исключении.

pRace(iterable)Метод возвращает объект обещания, как только объект обещания в итератореresolvedилиrejected, возвращенный объект обещания разрешит или отклонит соответствующее значение.

Пример использования

// p-race/p-race.test.js
import delay from "delay";
import pRace from "p-race";

const inputs = [delay(50, { value: 1 }), delay(100, { value: 2 })];

async function main() {
  const result = await pRace(inputs);
  console.dir(result); // 输出结果:1
}

main();

В приведенном выше примере мы импортировалиdelayМодули экспортируются по умолчаниюdelayметод, который можно использовать для отсрочки объекта Promise на заданное время. использоватьdelayмы создаем 2 объекта Promise, а затем используемpRaceдля обработки двух объектов Promise. После успешного выполнения приведенного выше кода командная строка всегда выводит1. Так почему же это так? Давайте проанализируемpRaceИсходный код функции.

Анализ исходного кода

// https://github.com/sindresorhus/p-race/blob/main/index.js
import isEmptyIterable from 'is-empty-iterable';

export default async function pRace(iterable) {
	if (isEmptyIterable(iterable)) {
		throw new RangeError('Expected the iterable to contain at least one item');
	}

	return Promise.race(iterable);
}

Наблюдая за приведенным выше исходным кодом, мы можем видеть, что вpRaceВнутренняя функция сначала определяет входящиеiterableЯвляется ли параметр пустым итерируемым объектом. Итерируемый объект, который определяет, является ли параметр пустым, черезisEmptyIterableКонкретный код функции выглядит следующим образом:

// https://github.com/sindresorhus/is-empty-iterable/blob/main/index.js
function isEmptyIterable(iterable) {
	for (const _ of iterable) {
		return false;
	}

	return true;
}

Когда найден пустой итерируемый объект,pRaceфункция будет напрямую бросатьRangeErrorаномальный. В противном случае используйтеPromise.raceAPI для реализации определенных функций. должны знать о том,Promise.raceСуществуют также проблемы совместимости с этим методом, как показано на следующем рисунке:

(Источник изображения --потрите newser.com/?search=pro…

Точно так же могут быть некоторые мелкие партнерыPromise.raceЕще не знаком с этим, это также очень часто задаваемый вопрос от руки. Итак, давайте напишем упрощенную версию дальшеPromise.race:

Promise.race = function (iterators) {
  return new Promise((resolve, reject) => {
    for (const iter of iterators) {
      Promise.resolve(iter)
        .then((res) => {
          resolve(res);
        })
        .catch((e) => {
          reject(e);
        });
    }
  });
};

p-forever

Run promise-returning & async functions repeatedly until you end it

Res или Fox на GitHub.com/sin…

Инструкции по применению

p-foreverПодходит для многократного выполненияpromise-returningилиasyncработать до тех пор, пока пользователь не завершит сцену. Модуль экспортирует значение по умолчаниюpForeverфункция со следующей сигнатурой:

pForever(fn, initialValue)

  • fn: Function: функция, которая будет выполняться повторно;
  • initialValue:Перейти кfnНачальное значение функции.

пониматьpForeverПосле подписи функции посмотрим, как эта функция используется.

Пример использования

// p-forever/p-forever.test.js
import delay from "delay";
import pForever from "p-forever";

async function main() {
  let index = 0;
  await pForever(async () => (++index === 10 ? pForever.end : delay(50)));
  console.log("当前index的值: ", index); // 输出结果:当前index的值: 10
}

main();

В приведенном выше примере передайтеpForeverфункциональныйfnФункция выполняется многократно до тех пор, покаfnвозврат функцииpForever.endзначение прекратит выполнение. Следовательно, после успешного выполнения приведенного выше кода вывод командной строки будет таким:Текущее значение индекса: 10.

Анализ исходного кода

// https://github.com/sindresorhus/p-forever/blob/main/index.js
const endSymbol = Symbol('pForever.end');

const pForever = async (function_, previousValue) => {
	const newValue = await function_(await previousValue);
	if (newValue === endSymbol) {
		return;
	}
	return pForever(function_, newValue);
};

pForever.end = endSymbol;
export default pForever;

Как видно из приведенного выше исходного кода,pForeverВнутренняя реализация функции не сложна. при оценкеnewValueценностьendSymbol, он возвращается напрямую. В противном случае он будет продолжать звонитьpForeverфункция. В дополнение к постоянному повторению задач, иногда мы хотим явно указать количество раз выполнения задачи.Для этого сценария мы можем использоватьp-timesмодуль.

p-times

Run promise-returning & async functions a specific number of times concurrently

Res или Fox на GitHub.com/sin…

Инструкции по применению

p-timesдля явного указанияpromise-returningилиasyncСценарии для количества раз выполнения функции. Модуль экспортирует значение по умолчаниюpTimesфункция со следующей сигнатурой:

pTimes(count, mapper, options): Promise

  • count: number: количество звонков;
  • mapper(index): Function: функция сопоставления, которая будет возвращать объект Promise или определенное значение после вызова этой функции;
  • options: object
    • concurrency: number-- количество параллелизма, по умолчаниюInfinity, минимальное значение1;
    • stopOnError: boolean—— Следует ли завершать работу при возникновении исключения, значение по умолчанию равноtrue.

пониматьpTimesПосле подписи функции посмотрим, как эта функция используется.

Пример использования

// p-times/p-times.test.js
import delay from "delay";
import pTimes from "p-times";

async function main() {
  console.time("start");
  const result = await pTimes(5, async (i) => delay(50, { value: i * 10 }), {
    concurrency: 3,
  });
  console.dir(result);
  console.timeEnd("start");
}

main();

В приведенном выше примере мы передаемpTimesКонфигурация функцийmapperКоличество раз, когда функция выполняется5раз и установите количество одновременных задач на3. Когда приведенный выше код выполняется успешно, командная строка выводит следующие результаты:

[ 0, 10, 20, 30, 40 ]
start: 116.090ms

Для приведенного выше примера вы можете изменитьconcurrencyзначение для сравнения времени выполнения выходной программы. ТакpTimesКак реализовать контроль параллелизма внутри функции? На самом деле, эта функция также используетpMapфункция для реализации контроля параллелизма.

Анализ исходного кода

// https://github.com/sindresorhus/p-times/blob/main/index.js
import pMap from "p-map";

export default function pTimes(count, mapper, options) {
  return pMap(
    Array.from({ length: count }).fill(),
    (_, index) => mapper(index),
    options
  );
}

существуетpTimesфункция, пройдетArray.fromметод для создания массива указанной длины, а затем передатьfillспособ заполнения. Наконец, поместите массив,mapperфункция иoptionsОбъект конфигурации, называемый входным параметромpMapфункция. Пишущий здесь, брат А Бао считает, чтоpMapФункции, предоставляемые функцией, довольно мощные, и многие модули используются внутри.pMapфункция.

p-pipe

Compose promise-returning & async functions into a reusable pipeline

Res или Fox на GitHub.com/sin…

Инструкции по применению

p-pipeПрименить кpromise-returningилиasyncФункции объединяются в многократно используемые конвейеры. Модуль экспортирует значение по умолчаниюpPipeфункция со следующей сигнатурой:

pPipe(input...)

  • input: Function: функция, которая, как ожидается, вернет обещание или любое значение при вызове.

пониматьpPipeПосле подписи функции посмотрим, как эта функция используется.

Пример использования

// p-pipe/p-pipe.test.js
import pPipe from "p-pipe";

const addUnicorn = async (string) => `${string} Unicorn`;
const addRainbow = async (string) => `${string} Rainbow`;

const pipeline = pPipe(addUnicorn, addRainbow);

(async () => {
  console.log(await pipeline("❤️")); // 输出结果:❤️ Unicorn Rainbow
})();

В приведенном выше примере мы передаемpPipeфункция положитьaddUnicornа такжеaddRainbowЭти две функции объединены в новый многоразовый конвейер. Порядок выполнения комбинированных функций слева направо, поэтому после успешного выполнения приведенного выше кода командная строка выведет❤️ Единорог Радуга.

Анализ исходного кода

// https://github.com/sindresorhus/p-pipe/blob/main/index.js
export default function pPipe(...functions) {
	if (functions.length === 0) {
		throw new Error('Expected at least one argument');
	}

	return async input => {
		let currentValue = input;

		for (const function_ of functions) {
			currentValue = await function_(currentValue); // eslint-disable-line no-await-in-loop
		}
		return currentValue;
	};
}

Как видно из приведенного выше кода, вpPipeВнутри функции используетсяfor...ofзаявление иasync/awaitфункция для реализации функциональности конвейера. После анализаpromise-funПосле 10 модулей в проекте почувствуй себя сноваasync/awaitФункции приносят большое удобство интерфейсному асинхронному программированию. На самом деле, для асинхронных сценариев помимо использованияpromise-funВ дополнение к модулям, включенным в проект, вы также можете использоватьasyncилиneo-asyncВспомогательные функции, предоставляемые этими двумя модулями асинхронной обработки. В проекте Webpack используетсяneo-asyncЭтот модуль, который автор модуля желает заменитьasyncмодуль для повышения производительности. Друзьям, которым необходимо иметь дело с асинхронными сценариями, рекомендуется часто тратить время на просмотрneo-asyncэтого модуляофициальная документация.

Суммировать

promise-funПроекты были записаны50модули, связанные с Promises, автор проектаsindresorhusЛично разработан48Этот модуль действительно является большой коровой с открытым исходным кодом, работающей полный рабочий день. Из-за ограниченного пространства брат Абао представил только10более часто используемый модуль. На самом деле, проект также содержит несколько довольно хороших модулей, таких какp-queue,p-any,p-some,p-debounce,p-throttleа такжеp-timeoutЖдать. Заинтересованные партнеры могут ознакомиться с другими модулями самостоятельно.

Справочные ресурсы