⚠️ This article is the first contract article in the nuggets community. It is prohibited to reprint without authorization

Recently, While reviewing the contents of the CLI (Command Line Interface), Po ge became interested in Lerna and began to “chew” its source code. In package.json, you need to add p-* dependencies to your dependencies. In package.json, you need to add p-* dependencies to your dependencies.

{
  "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"}}Copy the code

Tip: If you want to know how Bob reads open source projects, I read this article about several good open source projects using these ideas and techniques.

Then He found promise-Fun (3.5K). The project’s author, Sindresorhus, is a full-time open-source giant with 43.9K followers on Github. He also maintains several excellent open source projects such as Awesome (167K), awl-Nodejs (42K), GOT (9.8K), ORA (7.1K) and ScreenFull.js (6.1K).

(Photo credit – github.com/sindresorhu…

The Promise-Fun project is a collection of 48 promise-related modules written by Sindresorhus, such as the p-Map, P-Map-Series, P-Pipe and P-Waterfall modules seen earlier. This article will screen some commonly used modules to analyze the usage and specific code implementation of each module in detail.

These modules provide a lot of useful methods, using these methods, can help us solve some common problems in our work, such as concurrency control, asynchronous task processing, especially dealing with multiple control flows such as Series, Waterfall, All, Race and Forever.

Guys, are you ready? Let’s take a journey through the promise- Fun project.

Initialize the sample project

Create a new learn-promise-fun project and execute NPM init -y in the root directory of the project to initialize the project. When this command runs successfully, a package.json file will be generated in the project root directory. Since many of the modules in the Promise-Fun project use ES Modules, we need to add the type field to the package.json file and set it to “Module” to make sure the sample code works.

Since apache native Node.js is v12.16.2, to run the ES Module, add the –experimental-modules command line option. If you don’t want to see warnings, you can add the –no-warnings command-line option. Additionally, to avoid entering the above command-line options each time we run the sample code, we can define the corresponding NPM script command in the scripts field of package.json, as shown below:

{
  "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". }},Copy the code

After completing the project initialization, let’s review the characteristics of the reduce, Map, and Filter array methods most commonly used:

Tip: The above image was created via 👉 carbon.now.sh/ online web page.

The array.prototype. reduce method in the figure is familiar, which performs a reducer function on each element in the Array and summarizes the results into a single return value. The following is an example:

const array1 = [1.2.3.4];
const reducer = (accumulator, currentValue) = > accumulator + currentValue;

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

The reducer function receives four parameters:

  • Acc (Accumulator) : is an Accumulator
  • Cur (Current Value) : indicates the Current Value
  • Idx (Current Index) : Current Index
  • SRC (Source Array) : indicates the Source Array

Next, the P-reduce module provides similar functionality to the array.prototype. reduce method.

p-reduce

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

Github.com/sindresorhu…

Directions for use

P-reduce applies to the scenario in which asynchronous resources need to be calculated. By default, this module exports a pReduce function with the following signature:

pReduce(input, reducer, initialValue): Promise

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

Now that we know the signature of the pReduce function, let’s look at how it is used.

Use the sample

// 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); // Output: 15
}

main();
Copy the code

In the example above, we imported the delay method that the Delay module exports by default, which can be used to delay a Promise object for a given amount of time. The Promise state will become Resolved after a given amount of time. By default, the delay function is implemented inside the delay module through the setTimeout API. In the example, delay(50, {value: 6}) means that after a delay of 50ms, the return value of the Promise object is 6. Inside the main function, we use the pReduce function to calculate the sum of elements in the inputs array. When the above code runs successfully, the command line prints 15.

Let’s examine how pReduce is implemented internally.

Source code analysis

// 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](); // Get the iterator
    let index = 0; / / index value

    const next = async (total) => {
      const element = iterator.next(); // Get the next item

      if (element.done) { // Check whether the iterator is finished iterating
        resolve(total);
        return;
      }

      try {
        const [resolvedTotal, resolvedValue] = await Promise.all([
          total,
          element.value,
        ]);
        // Iterate over the next item
        // reducer(previousValue, currentValue, index): Function
        next(reducer(resolvedTotal, resolvedValue, index++));
      } catch(error) { reject(error); }};// Start the iteration using the initial value
    next(initialValue);
  });
}
Copy the code

In the above code, the core process is iterating over and over again by taking iterators inside iterable objects. In addition, in the pReduce function, the promise. all method is used, which will return a Promise object. When all the entered Promise objects are in resolved state, the returned Promise objects will be in the form of an array. Returns the result of each Promise object resolve. When the state of any entered Promise object changes to Rejected, the returned Promise object rejects the corresponding error message.

However, it should be noted that the promise.all method has compatibility problems, as shown in the figure below:

(Photo credit – caniuse.com/?search=Pro…

For those of you who are not familiar with Promise.all, it is a frequently written question. So, let’s write a simple version of Promise. All:

Promise.all = function (iterators) {
  return new Promise((resolve, reject) = > {
    if(! iterators || iterators.length ===0) {
      resolve([]);
    } else {
      let count = 0; // count to determine whether all tasks are completed
      let result = []; // Result array
      for (let i = 0; i < iterators.length; i++) {
        // Given that iterators[I] may be generic objects, wrap them as Promise objects
        Promise.resolve(iterators[i]).then(
          (data) = > {
            result[i] = data; // Save the corresponding results in order
            // When all tasks are completed, the result is returned
            if(++count === iterators.length) { resolve(result); }},(err) = > {
            reject(err); Reject () is called when any of the Promise objects fail to execute
            return; }); }}}); };Copy the code

p-map

Map over promises concurrently

Github.com/sindresorhu…

Directions for use

P-map is suitable for scenarios where promise-RETURNING or Async functions are run multiple times with different inputs. The difference from the promise.all method introduced earlier is that you can control concurrency and also decide whether to stop iterating if an error occurs. By default, this module exports a pMap function with the following signature:

pMap(input, mapper, options): Promise

  • input: Iterable<Promise | unknown>
  • mapper(element, index): Function
  • options: object
    • concurrency: number— Number of concurrent requests, defaultInfinity, the minimum value is1;
    • stopOnError: boolean— Whether to terminate when an exception occurs. The default value istrue.

Now that we know the signature of the pMap function, let’s look at how it can be used.

Use the sample

// 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); // Output: [200, 100, 50]
  console.timeEnd("start");
}

main();
Copy the code

In the example above, we also use the delay function exported by the Delay module to implement the delay function. After the above code is successfully executed, the command line displays the following information:

[200, 100, 50] Start: 368.708msCopy the code

Once the concurrency property is set to 2, run the code again. The command line will print the following:

[200, 100, 50] Start: 210.322msCopy the code

Looking at the output above, we can see that when the number of concurrent is 1, the running time of the program is greater than 350ms. When the number of concurrent tasks is 2, multiple tasks are executed in parallel, so the program takes just over 210 milliseconds to run. So how does pMap function implement concurrency control internally? Below to analyze the pMap function source code.

Source code analysis

// 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) = > {
    // omit the parameter verification code
    const result = []; // Store the return result
    const errors = []; // Store the exception object
    const skippedIndexes = []; // Saves an array of skip index values
    const iterator = iterable[Symbol.iterator](); // Get the iterator
    let isRejected = false; // Indicates whether an exception occurs
    let isIterableDone = false; // Indicates whether the iteration has been completed
    let resolvingCount = 0; // Number of tasks being processed
    let currentIndex = 0; // Current index

    const next = () = > {
      if (isRejected) { // If an exception occurs, return directly
        return;
      }

      const nextItem = iterator.next(); // Get the next item
      const index = currentIndex; // Record the current index value
      currentIndex++;

      if (nextItem.done) { // Check whether the iterator is finished iterating
        isIterableDone = true;

        // Determine if all tasks have been completed
        if (resolvingCount === 0) { 
          if(! stopOnError && errors.length >0) { // Exception handling
            reject(new AggregateError(errors));
          } else {
            for (const skippedIndex of skippedIndexes) {
              // Delete skipped values, otherwise there will be empty placeholders
              result.splice(skippedIndex, 1); 
            }
            resolve(result); // Returns the final processing result}}return;
      }

      resolvingCount++; // The number of tasks being processed increases by 1

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

          if (isRejected) {
            return;
          }

          // Call the mapper function for value processing
          const value = await mapper(element, index);
          // To handle the skip case, pMapSkip can be returned in mapper function to skip the current item
          For example, in an exception catch statement, return the value of pMapSkip
          if (value === pMapSkip) { // pMapSkip = Symbol("skip")
            skippedIndexes.push(index);
          } else {
            result[index] = value; // Save the return value by index
          }

          resolvingCount--;
          next(); // Iterate over the next item
        } catch (error) {
          if (stopOnError) { // Whether to terminate when an exception occurs. The default value is true
            isRejected = true;
            reject(error);
          } else{ errors.push(error); resolvingCount--; next(); }}}) (); };// Execute tasks concurrently according to the concurrency value of the configuration
    for (let index = 0; index < concurrency; index++) {
      next();
      if (isIterableDone) {
        break; }}}); }export const pMapSkip = Symbol("skip");
Copy the code

The processing logic inside the pMap function is fairly clear, encapsulating the core processing logic in the next function. After the pMap function is called, the next function is concurrently called based on the concurrency value of the configuration. Inside the next function, async/await is used to control the execution of the task.

In the pMap function, the author cleverly designs pMapSkip. When pMapSkip is returned in mapper, the value of the index item is removed from the returned result array. After understanding the role of pMapSkip, let’s take a simple example:

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();
Copy the code

In the above code, the inputs array contains pMapSkip. PMapSkip will be filtered out after the inputs array is processed using pMap, so the final result is [200, 50].

p-filter

Filter promises concurrently

Github.com/sindresorhu…

Directions for use

P-filter is suitable for scenarios where the promise-RETURNING or Async functions are run multiple times with different inputs and the returned results are filtered. By default, this module exports a pFilter function with the following signature:

pFilter(input, filterer, options): Promise

  • input: Iterable<Promise | any>
  • filterer(element, index): Function
  • options: object
    • concurrency: number— Number of concurrent requests, defaultInfinity, the minimum value is1.

Now that we know the signature of the pFilter function, let’s look at how it is used.

Use the sample

// 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); // Output result: [1, 3]
}

main();
Copy the code

In the above example, we use the pFilter function to apply the (x) => x % 2 filter to the Inputs array that contains the Promise object. When the above code runs successfully, the command line prints [1, 3].

Source code analysis

// 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]);
};
Copy the code

As you can see from the above code, inside the pFilter function, we use the pMap and promise.all functions we’ve already introduced. To understand the above code, we need to review the key code of the pMap function:

// 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](); // Get the iterator
  let currentIndex = 0; // Current index
  
  const next = () = > {
    const nextItem = iterator.next(); // Get the next item
    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; // Save the return value by index
          }
          resolvingCount--;
          next(); // Iterate over the next item
        } catch (error) {
          // omit exception handling code}}) (); }}Copy the code

All ([filterer(element, index), element]), So the return value of the await mapper(Element, index) expression is an array. The first item of the array is the result of the Filterer filter, and the second item of the array is the value of the current element. So when the pMap function is called, its return value is a two-dimensional array. So after getting the return value of the pMap function, the following statement is used to process the return value:

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

In the previous pFilter example, in addition to inputs that can contain a Promise object, our filterer filter can also return a Promise object:

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();
Copy the code

After the above code is successfully executed, the command line output is also [1, 3]. Ok, now we have introduced the three modules p-Reduce, P-Map and P-filter. Let’s continue to introduce another module — p-Waterfall.

p-waterfall

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

Github.com/sindresorhu…

Directions for use

P-waterfall is suitable for scenarios where a promise-RETURNING or async function is executed serially and the return result of the previous function is automatically passed to the next one. This module exports a pWaterfall function by default, which has the following signature:

pWaterfall(tasks, initialValue): Promise

  • tasks: Iterable<Function>
  • initialValue: unknown: will be the first missionpreviousValue

After knowing the signature of the pWaterfall function, let’s look at how it can be used.

Use the sample

// 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); // Output: 6
}

main();
Copy the code

In the example above, we created three tasks and then used the pWaterfall function to execute them. When the above code is successfully executed, the command line prints 6. The corresponding execution process is shown in the figure below:

Source code analysis

// 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);
}
Copy the code

In the pWaterfall function, pReduce function introduced previously will be used to realize waterfall process control. Again, to understand the internal control flow, we need to review the implementation of the pReduce function:

export default async function pReduce(iterable, reducer, initialValue) {
  return new Promise((resolve, reject) = > {
    const iterator = iterable[Symbol.iterator](); // Get the iterator
    let index = 0; / / index value

    const next = async (total) => {
      const element = iterator.next(); // Get the next item

      if (element.done) {
        // Check whether the iterator is finished iterating
        resolve(total);
        return;
      }

      try {
        // State of next function called for the first time:
        // 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); }};// Start the iteration using the initial value
    next(initialValue);
  });
}
Copy the code

Now we already know that the pWaterfall function will pass the output of the previous task as input to the next task. But sometimes, when executing each task sequentially, we don’t care about the return value of each task. For this case, we can consider using the pSeries function provided by the P-series module.

p-series

Run promise-returning & async functions in series

Github.com/sindresorhu…

Directions for use

P-series is suitable for scenarios in which promise-RETURNING or Async functions are executed serially.

Use the sample

// 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); // Output result: [2, 4, 6]
}

main();
Copy the code

In the example above, we created three tasks and then used the pSeries function to execute them. When the above code is successfully executed, the command line prints [2, 4, 6]. The corresponding execution process is shown in the figure below:

Source code analysis

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

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

	return results;
}
Copy the code

Inside the pSeries function is the for… Of statement and async/await feature to implement serial task flow control. Therefore, in real projects, you can easily implement serial task flow control without using this module.

p-all

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

Github.com/sindresorhu…

Directions for use

P-all is suitable for scenarios where promise-RETURNING or async functions are executed concurrently. This module provides similar functionality to the Promise.all API, with the major difference that it allows you to limit the number of concurrent tasks. Async-pool: async-pool: async-pool: Async-pool: Async-pool: Async-pool: Async-pool: Async-pool: Async-pool: Async-pool This article.

Let’s continue with the p-all module, which exports a pAll function by default with the following signature:

pAll(tasks, options)

  • tasks: Iterable<Function>
  • options: object
    • concurrency: number— Number of concurrent requests, defaultInfinity, the minimum value is1;
    • stopOnError: boolean— Whether to terminate when an exception occurs. The default value istrue.

Use the sample

// 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();
Copy the code

In the example above, we created three asynchronous tasks and then used the pAll function to execute the created tasks. After the above code is successfully executed, the command line displays the following information:

[1, 2, 8] Start: 312.561msCopy the code

Once the concurrency property is set to 2, run the code again. The command line will print the following:

[1, 2, 8] Start: 209.469msCopy the code

It can be seen that when the number of concurrent is 1, the running time of the program is greater than 300ms. If the number of concurrent tasks is 2, the first two tasks are parallel, so the program takes just over 200 milliseconds to run.

Source code analysis

// 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);
}
Copy the code

It is obvious that inside the pAll function, concurrency control is achieved through the pMap function provided by the P-Map module. If you are not sure about the internal implementation of the pMap function, go back and read the p-Map module again. Next, let’s move on to another module, P-Race.

p-race

A better Promise.race()

Github.com/sindresorhu…

Directions for use

The p-race module fixes a “stupid” behavior in the Promise.race API. When you call the Promise.race API with an empty iterable, you return a Promise object that is always in a pending state, which can cause problems that are very difficult to debug. If an empty iterable is passed to the pRace function provided by the P-race module, the function will immediately raise a RangeError: Expected the iterable to contain at least one item.

The pRace(iterable) method returns a promise object that will resolve or reject once an iterator promise object is resolved or Rejected.

Use the sample

// 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); // Output: 1
}

main();
Copy the code

In the example above, we imported the delay method that the Delay module exports by default, which can be used to delay a Promise object for a given amount of time. Using the delay function, we create two Promise objects, and then use the pRace function to process them. After the above code runs successfully, the command line always prints 1. So why does this happen? Let’s analyze the source code of pRace function.

Source code analysis

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

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

	return Promise.race(iterable);
}
Copy the code

The pRace function checks whether the iterable passed in is an empty iterable. The isEmptyIterable function is used to check whether an iterable is empty. The code for isEmptyIterable is as follows:

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

	return true;
}
Copy the code

When an iterable is found to be empty, the pRace function directly raises a RangeError. Otherwise, the promise.race API is used to implement specific functionality. It should be noted that the promise.race method also has compatibility issues, as shown in the following figure:

(Photo credit – caniuse.com/?search=Pro…

Similarly, for those of you who may not be familiar with promise. race, it is also a very frequent handwritten question. So, let’s write a simple version of 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); }); }}); };Copy the code

p-forever

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

Github.com/sindresorhu…

Directions for use

P-forever is suitable for scenarios that require repeated execution of promise-RETURNING or Async functions until the user terminates. This module exports a pForever function by default, which has the following signature:

pForever(fn, initialValue)

  • fn: Function: a function that is executed repeatedly;
  • initialValuePassed:fnThe initial value of the function.

Now that we know about the signature of the pForever function, let’s take a look at how it’s used.

Use the sample

// 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("Current index value:", index); // Output result: Current index value: 10
}

main();
Copy the code

In the example above, the fn function passed to pForever is repeated until the fn function returns the value of pforever.end. Therefore, after the above code is successfully executed, the command line output is: the current index value: 10.

Source code analysis

// 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;
Copy the code

As you can see from the above source code, the internal implementation of the pForever function is not complicated. When the value of newValue is determined to be endSymbol, the value is returned. Otherwise, the pForever function will continue to be called. In addition to repeating tasks all the time, we sometimes want to explicitly specify how many times a task should be executed, for which we can use the P-Times module.

p-times

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

Github.com/sindresorhu…

Directions for use

P-times is suitable for scenarios that explicitly specify the number of promise-RETURNING or async function executions. By default, this module exports a pTimes function with the following signature:

pTimes(count, mapper, options): Promise

  • count: number: Number of calls;
  • mapper(index): Function: mapper function, which returns a Promise object or a specific value;
  • options: object
    • concurrency: number— Number of concurrent requests, defaultInfinity, the minimum value is1;
    • stopOnError: boolean— Whether to terminate when an exception occurs. The default value istrue.

Now that we know the signature of the pTimes function, let’s look at how it is used.

Use the sample

// 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();
Copy the code

In the example above, we use the pTimes function to configure the mapper function to execute 5 times and set the number of concurrent tasks to 3. When the above code runs successfully, the command line displays the following results:

[0, 10, 20, 30, 40] start: 116.090msCopy the code

For the example above, you can change the concurrency value to compare the output application runtime. So how does the pTimes function implement concurrency control internally? In fact, this function also uses the pMap function to achieve concurrency control.

Source code analysis

// 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
  );
}
Copy the code

In the pTimes function, an Array of the specified length is created using the array. from method, which is then filled with the fill method. Finally, the pMap function is called with the array, mapper function, and options configuration object as input parameters. The pMap function is used in many modules.

p-pipe

Compose promise-returning & async functions into a reusable pipeline

Github.com/sindresorhu…

Directions for use

P-pipe is suitable for combining promise-RETURNING or Async functions into reusable pipes. By default, this module exports a pPipe function with the following signature:

pPipe(input…)

  • input: Function: a function that is expected to return Promise or any value when called.

Now that we know the signature of the pPipe function, let’s look at how it can be used.

Use the sample

// 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}) ();Copy the code

In the example above, we combine the addUnicorn and addRainbow functions into a new reusable pipe using the pPipe function. The composited functions are executed from left to right, so the command line prints ❤️ Unicorn Rainbow after the above code runs successfully.

Source code analysis

// 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;
	};
}
Copy the code

From the above code, it can be seen that inside pPipe function, for… The of statement and the async/await feature implement the functionality of the pipe. After analyzing the 10 modules in the Promise-Fun project, I once again felt the great convenience brought by the async/await feature to front-end asynchronous programming. In fact, for asynchronous scenarios, in addition to the modules included in the Promise-Fun project, you can also use the utility functions provided by the async or neo-Async asynchronous processing modules. In the Webpack project, the neo-Async module is used, which is intended to replace the Async module to provide better performance. If you need to deal with asynchronous scenarios a lot, take a look at the official documentation of neo-Async.

conclusion

Promise-fun is a collection of 50 promise-related modules. Sindresorhus, the author of the project, has developed 48 of them, making her a full-time open source champion. Due to limited space, Arbog only introduces 10 of the more commonly used modules. The project also includes some nice modules, such as p-Queue, P-any, P-some, p-Debounce, p-throttle, and p-timeout. Interested partners, you can learn about other modules.

The resources

  • MDN – Array. The prototype. The reduce ()
  • MDN – Array. The from
  • Promise.race with empty lists
  • Neo-async official document