1. The introduction of the hooks

Hook Hook Hook Hook Hook Hook Hook Hook It is usually a way for a system or framework to be opened up for upper-level application developers to execute code. For example, Vue’s lifecycle hooks are essentially component-defined hook functions that are called from within the framework at the appropriate time. In addition, Tapable used by Webpack is the hook application to play incisively and vividly, tapable is the most commendable is that it makes a good abstraction and classification of hook.

2. The expression of Hook

For developers, hooks usually exist in the form of Hook functions. The developer registers the hook function, and the system or framework decides when to call the hook function, which, in a sense, is similar to the event callback function. From the design of the data structure, we can use key-value pairs (hash tables, ordinary objects in JS) to represent the system-provided hooks, where the key represents the hook name and the value is the array of hook functions.

For example, the following code contains an init hook that registers three hook functions: fn1, fn2, and fn3.

const hooks = {
  init: [fn1, fn2, fn3]
}
Copy the code

If we think of a Hook as an abstract data type (ADT) like a stack or queue, then the set of operations of a Hook includes a Register and a Call. The simple implementation is:

// Register the hook
function regHook(hookName, hookFn) {
  if(! hooks[hookName]) hooks[hookName] = [] hooks[hookName].push(hookFn) }// Call the hook
function callHook(hookName, ... args) {
  hooks[hookName].forEach(fn= >fn(... args)) }Copy the code

3. Classification of Hook

3.1 Serial and parallel

According to the execution order of hook functions, they can be divided into:

  • Serial hooks: Hooks are called in the order in which they are registered. Subsequent hooks must wait until the previous one has finished executing. Serial hooks can be synchronous or asynchronous
  • Parallel hooks: Hooks are called sequentially, but can be executed simultaneously, meaning that subsequent hooks do not have to wait for previous hooks to complete. Obviously, parallel hooks must be asynchronous

3.2 Synchronous and asynchronous

According to the execution mode of hook function, it can be divided into:

  • Synchronous hook: Hook execution blocks the main thread, and the hook function returns to indicate the completion of hook execution
  • Asynchronous hooksHook execution does not block the main thread. The return of the hook function does not mean that the hook execution is completepromise.thenTo get the message that hook execution has finished

4. The Hook calls

Registering hooks is relatively simple. You simply add hook functions to the array of hook functions in sequence. To call a hook, you need to take a different method depending on the hook type.

4.1 Synchronous hook invocation

Synchronous hook calls are the simplest. Hook functions are called sequentially, and are executed serially.

function callSync(hookName, ... args) {
  hooks[hookName].forEach(fn= >fn(... args)) }Copy the code

4.2 Invocation of asynchronous hooks

Asynchronous hook calls are either serial or parallel.

4.2 Asynchronous Serial

4.2.1 Based on callback functions
function callAsyncSeries(hookName, done, ... args) {
  const fns = hooks[hookName]
  let i = fns.length
  let next = done
  while(i) {
    let fn = fns[--i]
    let _next = next
    next = (a)= >fn(... args, _next) } next() }Copy the code

Example:

regHook('asyncSer', (a, b, done) => { setTimeout((a)= > { console.log('timout 1', a, b); done() }, 1000) })
regHook('asyncSer', (a, b, done) => { setTimeout((a)= > { console.log('timout 2', a, b); done() }, 2000) })
regHook('asyncSer', (a, b, done) => { setTimeout((a)= > { console.log('timout 3', a, b); done() }, 3000) })

callAsyncSeries('asyncSer', () = > {console.log('done')},'aa'.'bb')

// timout 1 aa bb
// timout 2 aa bb
// timout 3 aa bb
// done
Copy the code
4.2.2 based on a Promise
function callPromiseSeries(hookName, ... args) {
  return new Promise(resolve= > {
    const fns = hooks[hookName]
    let i = fns.length
    let next = resolve
    while(i) {
      let fn = fns[--i]
      let _next = next
      next = (a)= >fn(... args).then(_next) } next() }) }Copy the code

Example:

regHook('promiseSer', (a, b) => {
  return new Promise(resolve= > 
    setTimeout((a)= > { console.log('promiseSer 1', a, b); resolve() }, 2000)
  )
})

regHook('promiseSer', (a, b) => {
  return new Promise(resolve= > 
    setTimeout((a)= > { console.log('promiseSer 2', a, b); resolve() }, 3000)
  )
})

regHook('promiseSer', (a, b) => {
  return new Promise(resolve= > 
    setTimeout((a)= > { console.log('promiseSer 3', a, b); resolve() }, 1000)
  )
})

callPromiseSeries('promiseSer'.'aa'.'bb').then((a)= > { console.log('done')})// promiseSer 1 aa bb
// promiseSer 2 aa bb
// promiseSer 3 aa bb
// done
Copy the code

4.3 Invocation of asynchronous parallel hooks

4.3.1 Based on callback functions
function callAsyncParallel(hookName, done, ... args) {
  const fns = hooks[hookName]
  let count = fns.length
  let _done = (a)= > {
    count--
    if (count === 0) {
      done()
    }
  }
  fns.forEach(fn= >fn(... args, _done)) }// Limit the number of concurrent requests
function callAsyncParallelN(hookName, done, N, ... args) {
  const fns = hooks[hookName]
  let count = fns.length
  let cur = 0
  let limit = N < fns.length ? N : fns.length
  let _done = (a)= > {
    count--
    if (count === 0) {
      done()
    } else if(cur < fns.length) { fns[cur++](... args, _done) } }for(; cur < limit; cur++) { fns[cur](... args, _done) } }Copy the code

Example:

regHook('asyncParallel', (a, b, done) => { setTimeout((a)= > { console.log('asyncParallel 1', a, b); done() }, 1000) })
regHook('asyncParallel', (a, b, done) => { setTimeout((a)= > { console.log('asyncParallel 2', a, b); done() }, 1000) })
regHook('asyncParallel', (a, b, done) => { setTimeout((a)= > { console.log('asyncParallel 3', a, b); done() }, 1000) })

callAsyncParallel('asyncParallel', () = > {console.log('done')},'aa'.'bb')
callAsyncParallelN('asyncParallel', () = > {console.log('done')},2.'aa'.'bb')
Copy the code
4.3.2 based on a Promise
function callPromiseParallel(hookName, ... args) {
  return new Promise(resolve= > {
    const fns = hooks[hookName]
    let count = fns.length
    let _done = (a)= > {
      count--
      if (count === 0) {
        resolve()
      }
    }
    fns.forEach(fn= >fn(... args).then(_done)) }) }// Limit the number of concurrent requests
function callPromiseParallelN(hookName, N, ... args) {
  return new Promise(resolve= > {
    const fns = hooks[hookName]
    let count = fns.length
    let cur = 0
    let limit = N < fns.length ? N : fns.length
    let _done = (a)= > {
      count--
      if (count === 0) {
        resolve()
      } else {
        if(cur < fns.length) { fns[cur++](... args).then(_done) } } }for(; cur < limit; cur++) { fns[cur](... args).then(_done) } }) }Copy the code

Example:

regHook('promiseParallel', (a, b) => {
  return new Promise(resolve= > 
    setTimeout((a)= > { console.log('promiseParallel 1', a, b); resolve() }, 1000)
  )
})

regHook('promiseParallel', (a, b) => {
  return new Promise(resolve= > 
    setTimeout((a)= > { console.log('promiseParallel 2', a, b); resolve() }, 1000)
  )
})

regHook('promiseParallel', (a, b) => {
  return new Promise(resolve= > 
    setTimeout((a)= > { console.log('promiseParallel 3', a, b); resolve() }, 1000)
  )
})

callPromiseParallel('promiseParallel'.'aa'.'bb').then((a)= > { console.log('done') })
callPromiseParallelN('promiseParallel'.2.'aa'.'bb').then((a)= > { console.log('done')})Copy the code

5. Code encapsulation

5.1 Synchronization Hooks

class Hook {
  constructor() {
    this.hookFns = []
  }
  reg(fn) {
    this.hookFns.push(fn) } call(... args) {this.hookFns.forEach(fn= >fn(... args)) } }Copy the code

5.2 Asynchronous callback Hooks

class AsyncHook extends Hook { call(... args, done) {const fns = this.hookFns
    let i = fns.length
    let next = done
    while(i) {
      let fn = fns[--i]
      let _next = next
      next = (a)= >fn(... args, _next) } next() } callParallel(... args, done) {const fns = this.hookFns
    let count = fns.length
    let _done = (a)= > {
      count--
      if (count === 0) {
        done()
      }
    }
    fns.forEach(fn= >fn(... args, _done)) } callParallelN(... args, done) {const fns = this.hookFns
    let count = fns.length
    let cur = 0
    let limit = N < fns.length ? N : fns.length
    let _done = (a)= > {
      count--
      if (count === 0) {
        done()
      } else if(cur < fns.length) { fns[cur++](... args, _done) } }for(; cur < limit; cur++) { fns[cur](... args, _done) } } }Copy the code

5.3 Asynchronous Promise Hooks

class PromiseHook extends Hook { call(... args) {return new Promise(resolve= > {
      const fns = this.hookFns
      let i = fns.length
      let next = resolve
      while(i) {
        let fn = fns[--i]
        let _next = next
        next = (a)= >fn(... args).then(_next) } next() }) } callParallel(... args) {return new Promise(resolve= > {
      const fns = this.hookFns
      let count = fns.length
      let _done = (a)= > {
        count--
        if (count === 0) {
          resolve()
        }
      }
      fns.forEach(fn= >fn(... args).then(_done)) }) } callParallelN(... args) {return new Promise(resolve= > {
      const fns = this.hookFns
      let count = fns.length
      let cur = 0
      let limit = N < fns.length ? N : fns.length
      let _done = (a)= > {
        count--
        if (count === 0) {
          resolve()
        } else {
          if(cur < fns.length) { fns[cur++](... args).then(_done) } } }for(; cur < limit; cur++) { fns[cur](... args).then(_done) } }) } }Copy the code