• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  ArrayPrototypeIndexOf,
5  ArrayPrototypeSlice,
6  ArrayPrototypeSplice,
7  ArrayPrototypePush,
8  FunctionPrototypeBind,
9} = primordials;
10
11const { setPromiseHooks } = internalBinding('async_wrap');
12const { triggerUncaughtException } = internalBinding('errors');
13
14const { kEmptyObject } = require('internal/util');
15const { validatePlainFunction } = require('internal/validators');
16
17const hooks = {
18  init: [],
19  before: [],
20  after: [],
21  settled: [],
22};
23
24function initAll(promise, parent) {
25  const hookSet = ArrayPrototypeSlice(hooks.init);
26  const exceptions = [];
27
28  for (let i = 0; i < hookSet.length; i++) {
29    const init = hookSet[i];
30    try {
31      init(promise, parent);
32    } catch (err) {
33      ArrayPrototypePush(exceptions, err);
34    }
35  }
36
37  // Triggering exceptions is deferred to allow other hooks to complete
38  for (let i = 0; i < exceptions.length; i++) {
39    const err = exceptions[i];
40    triggerUncaughtException(err, false);
41  }
42}
43
44function makeRunHook(list) {
45  return (promise) => {
46    const hookSet = ArrayPrototypeSlice(list);
47    const exceptions = [];
48
49    for (let i = 0; i < hookSet.length; i++) {
50      const hook = hookSet[i];
51      try {
52        hook(promise);
53      } catch (err) {
54        ArrayPrototypePush(exceptions, err);
55      }
56    }
57
58    // Triggering exceptions is deferred to allow other hooks to complete
59    for (let i = 0; i < exceptions.length; i++) {
60      const err = exceptions[i];
61      triggerUncaughtException(err, false);
62    }
63  };
64}
65
66const beforeAll = makeRunHook(hooks.before);
67const afterAll = makeRunHook(hooks.after);
68const settledAll = makeRunHook(hooks.settled);
69
70function maybeFastPath(list, runAll) {
71  return list.length > 1 ? runAll : list[0];
72}
73
74function update() {
75  const init = maybeFastPath(hooks.init, initAll);
76  const before = maybeFastPath(hooks.before, beforeAll);
77  const after = maybeFastPath(hooks.after, afterAll);
78  const settled = maybeFastPath(hooks.settled, settledAll);
79  setPromiseHooks(init, before, after, settled);
80}
81
82function stop(list, hook) {
83  const index = ArrayPrototypeIndexOf(list, hook);
84  if (index >= 0) {
85    ArrayPrototypeSplice(list, index, 1);
86    update();
87  }
88}
89
90function makeUseHook(name) {
91  const list = hooks[name];
92  return (hook) => {
93    validatePlainFunction(hook, `${name}Hook`);
94    ArrayPrototypePush(list, hook);
95    update();
96    return FunctionPrototypeBind(stop, null, list, hook);
97  };
98}
99
100const onInit = makeUseHook('init');
101const onBefore = makeUseHook('before');
102const onAfter = makeUseHook('after');
103const onSettled = makeUseHook('settled');
104
105function createHook({ init, before, after, settled } = kEmptyObject) {
106  const hooks = [];
107
108  if (init) ArrayPrototypePush(hooks, onInit(init));
109  if (before) ArrayPrototypePush(hooks, onBefore(before));
110  if (after) ArrayPrototypePush(hooks, onAfter(after));
111  if (settled) ArrayPrototypePush(hooks, onSettled(settled));
112
113  return () => {
114    for (const stop of hooks) {
115      stop();
116    }
117  };
118}
119
120module.exports = {
121  createHook,
122  onInit,
123  onBefore,
124  onAfter,
125  onSettled,
126};
127