Recently, the author of Mini-Vue, who owns nearly 4K star, posted a paragraph like this on wechat moments:

So in the end let big Trey brother sleepless vuE3 source long what kind?

Vue3 source clever code

async function runParallel(maxConcurrency, source, iteratorFn) {
    const ret = []
    const executing = []
    for(const item of source){
        const p = Promise.resolve().then(() = > iteratorFn(item, source))
        ret.push(p)
        if(maxConcurrency <= source.length) {
           const e = p.then(() = > executing.splice(executing.indexOf(e), 1))
           executing.push(e)
            if(executing.length >= maxConcurrency) {
               await Promise.race(executing)
            }
        }
    }
    return Promise.all(ret)
}
Copy the code

Basic knowledge to use

There are several important points to master in the vue3 package code: promise. race, promise. all, for of, Event loop

Pomise. The use of all

Promise.all can wrap multiple Promise instances into a new Promise instance. Also, success and failure return different values, with success returning an array of results and failure returning the first rejected state.

Promise. The use of the race

Promse.race, as the name suggests, means a race, which means that a Promise. Race ([P1, P2, P3]) returns the fastest result, regardless of whether the result itself is a success or a failure.

The difference between for in and for of

For in iterates over the array index (that is, the key name), while for of iterates over the array element value

Review the Event loop

There are already plenty of tutorials on the Event Loop, but I’m just going to do a quick review for myself. Because JavaScript is the language of a single thread, so the same time can only do one thing, to do other things on the asynchronous task queue, such as the current execution stack code execution, will go to asynchronous task queue in the asynchronous extraction to the execution stack task execution, if the current stack and produce asynchronous tasks, The asynchronous task is then placed in the task queue and retrieved after the execution of the code in the execution stack. Such a loop is called an Event loop. Note that: Asynchronous tasks are divided into macro tasks and micro tasks. The micro tasks are executed first and all the micro tasks in the asynchronous task queue are cleared. After the micro tasks are executed, the browser redraws and then executes the macro tasks in the asynchronous task queue.

The runParallel function is explained in detail

Let’s take a closer look at this code

MaxConcurrency Maximum number of parallel executions

Source Array of asynchronous tasks

IteratorFn Asynchronous task execution function

Const ret = [] constructs an array of asynchronously executed functions for the promise.all call.

Const executing = [] An array of asynchronous tasks that are executed in parallel

For (const item of source) loops asynchronous tasks

Const p = promise.resolve ().then(() => iteratorFn(item, source)) adds a task to the asynchronous task queue

Ret.push (p) constructs an array of asynchronous functions that promise.all calls execute

If (maxConcurrency <= source.length) There is no need to limit concurrency if the maximum concurrency is less than or equal to the number of asynchronous tasks

Const e = p hen(() => executing.splice(executing.indexof (e), 1)) In the then method, you need to delete a task that has been executed in a queue and return a result

Executing.push(e) Adds an asynchronous task to a parallel pool

If (executing. Length >= maxConcurrency) The number of concurrent asynchronous tasks is greater than or equal to the maximum number of concurrent tasks

Race (Executing) Async tasks in a queue array of asynchronous tasks that are executing in parallel

A diagram of Big Trego

Here is a diagram of Big Trego’s circle of friends:

Collect task

It maximizes parallelism

How do I know the completion of a parallel task?

Simulation implementation run

Let’s simulate the implementation:

async function runParallel(maxConcurrency, source, iteratorFn) {
    const ret = []
    const executing = []
    for (const item of source) {
        const p = Promise.resolve().then(() = > iteratorFn(item, source))
        ret.push(p)
        if (maxConcurrency <= source.length) {
            const e = p.then(() = >{
                console.log('executing.splice', item)
                executing.splice(executing.indexOf(e), 1)
            })
            executing.push(e)
            console.log('222')
            if (executing.length >= maxConcurrency) {console.log('333', e)
                 await Promise.race(executing)
                 console.log('444')}}}return Promise.all(ret)
}
// Simulate the implementation code
const source = [2000.1000.3000.6500]
async function build(target) {console.log('target', target)
    await new Promise((resolve, reject) = > {
         setTimeout(() = > {
            console.log('build', target)
            resolve('success')    
         }, target);
    })
}
runParallel(2, source, build)
Copy the code

Printout from the console

Execution process parsing:

In order to loop 2000 for the first time, add an asynchronous function to ret and executing a task, and then add an asynchronous task 2000 to the queue. Console. log(‘222’) is executed because the following conditions are not met.

In executing a executing task, you need to create a new asynchronous function in ret and executing a executing task. In executing a executing task, you need to create a new asynchronous function in ret and executing a executing task. In executing a executing task, you need to create a new asynchronous function in RET and executing a executing task. Race (Executing) : Subtask 2000 and Subtask 1000 will be executed in the asynchronous task queue. Synchronous task queue is a first-in, first-out task. Create a build 1000 and execute a then executing executing method to create a build 1000. Splice 1000 and then executing an asynchronous function of executing a 1000 to create a 444.

The third time, 3000, the process is the same as the second time. In executing a executing task, you need to create a new asynchronous function (RET) and a new asynchronous task (3000). In executing a executing task, you need to print 222. In executing a executing task, you need to print 333 and a Promise. Race (Executing) wait for a Promise. Race (executing) to execute a task in the asynchronous task queue. Only one asynchronous task 3000 is in the asynchronous task queue, so print target 3000 and wait for a Promise. Splice 2000: executing a build 2000 and executing a 3000. Then executing a build 2000 and executing a then executing a splice 2000 and executing a 444

On the fourth cycle of 6500, the process is the same as on the third cycle.

Vue3 source code in the application

Finally, let’s look at what this function does in VUe3.

First, the source location of this function in vue3 is script/build.js

The specific content

I then searched the entire project and found only one reference to this function

async function buildAll(targets) {
  await runParallel(require('os').cpus().length, targets, build)
}
Copy the code

So what does require(‘ OS ‘).cpus() mean? This will search the OS module of Node.js

Nodejs OS module

The Node.js OS module provides some basic system operation functions. We can introduce this module in the following way:

The serial number methods describe
1 os.tmpdir() Returns the default temporary folder of the operating system.
2 os.endianness() Returns the byte order of the CPU, possibly “BE” or “LE”.
3 os.hostname() Return the host name of the operating system.
4 os.type() Return the operating system name
5 os.platform() Returns the operating system name at compile time
6 os.arch() Returns the CPU architecture of the operating system. Possible values are X64, ARM, and IA32.
7 os.release() Returns the distribution of the operating system.
8 Os.uptime () Returns the operating system running time, in seconds.
9 os.loadavg() Returns an array of 1, 5, and 15-minute load averages.
10 os.totalmem() Returns the total amount of system memory, in bytes.
11 os.freemem() Returns the amount of free memory of the operating system in bytes.
12 os.cpus() Returns an array of objects containing information about each INSTALLED CPU/ kernel: model, speed in MHz, time (an object containing the number of CPU/ kernel milliseconds used by User, nice, SYS, Idle, and IRQ).
13 os.networkInterfaces() Get a list of network interfaces.

So require(‘ OS ‘).cpus().length is the number of cpus in the current system, so vue3 is the number of cpus in the current system.

What does the targets parameter mean

So what does the second parameter targets mean? See the source code targets is generated by the following code

const targets = (exports.targets = fs.readdirSync('packages').filter(f= > {
  if(! fs.statSync(`packages/${f}`).isDirectory()) {
    return false
  }
  const pkg = require(`.. /packages/${f}/package.json`)
  if(pkg.private && ! pkg.buildOptions) {return false
  }
  return true
}))
Copy the code

Json file that contains package.json files and cannot be private and cannot have no packaging options

The final build function is the concrete package execution function.

Conclusion:

The Promise pool allows you to limit the maximum number of parallel runs by waiting for promises.