summaryrefslogtreecommitdiff
path: root/lib/internal/promise_hooks.js
blob: b58f2ba1cc672f16687843474bdbd8fb92d7b5ac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
'use strict';

const {
  ArrayPrototypeIndexOf,
  ArrayPrototypeSlice,
  ArrayPrototypeSplice,
  ArrayPrototypePush,
  FunctionPrototypeBind,
} = primordials;

const { setPromiseHooks } = internalBinding('async_wrap');
const { triggerUncaughtException } = internalBinding('errors');

const { kEmptyObject } = require('internal/util');
const { validatePlainFunction } = require('internal/validators');

const hooks = {
  init: [],
  before: [],
  after: [],
  settled: [],
};

function initAll(promise, parent) {
  const hookSet = ArrayPrototypeSlice(hooks.init);
  const exceptions = [];

  for (let i = 0; i < hookSet.length; i++) {
    const init = hookSet[i];
    try {
      init(promise, parent);
    } catch (err) {
      ArrayPrototypePush(exceptions, err);
    }
  }

  // Triggering exceptions is deferred to allow other hooks to complete
  for (let i = 0; i < exceptions.length; i++) {
    const err = exceptions[i];
    triggerUncaughtException(err, false);
  }
}

function makeRunHook(list) {
  return (promise) => {
    const hookSet = ArrayPrototypeSlice(list);
    const exceptions = [];

    for (let i = 0; i < hookSet.length; i++) {
      const hook = hookSet[i];
      try {
        hook(promise);
      } catch (err) {
        ArrayPrototypePush(exceptions, err);
      }
    }

    // Triggering exceptions is deferred to allow other hooks to complete
    for (let i = 0; i < exceptions.length; i++) {
      const err = exceptions[i];
      triggerUncaughtException(err, false);
    }
  };
}

const beforeAll = makeRunHook(hooks.before);
const afterAll = makeRunHook(hooks.after);
const settledAll = makeRunHook(hooks.settled);

function maybeFastPath(list, runAll) {
  return list.length > 1 ? runAll : list[0];
}

function update() {
  const init = maybeFastPath(hooks.init, initAll);
  const before = maybeFastPath(hooks.before, beforeAll);
  const after = maybeFastPath(hooks.after, afterAll);
  const settled = maybeFastPath(hooks.settled, settledAll);
  setPromiseHooks(init, before, after, settled);
}

function stop(list, hook) {
  const index = ArrayPrototypeIndexOf(list, hook);
  if (index >= 0) {
    ArrayPrototypeSplice(list, index, 1);
    update();
  }
}

function makeUseHook(name) {
  const list = hooks[name];
  return (hook) => {
    validatePlainFunction(hook, `${name}Hook`);
    ArrayPrototypePush(list, hook);
    update();
    return FunctionPrototypeBind(stop, null, list, hook);
  };
}

const onInit = makeUseHook('init');
const onBefore = makeUseHook('before');
const onAfter = makeUseHook('after');
const onSettled = makeUseHook('settled');

function createHook({ init, before, after, settled } = kEmptyObject) {
  const hooks = [];

  if (init) ArrayPrototypePush(hooks, onInit(init));
  if (before) ArrayPrototypePush(hooks, onBefore(before));
  if (after) ArrayPrototypePush(hooks, onAfter(after));
  if (settled) ArrayPrototypePush(hooks, onSettled(settled));

  return () => {
    for (const stop of hooks) {
      stop();
    }
  };
}

module.exports = {
  createHook,
  onInit,
  onBefore,
  onAfter,
  onSettled,
};