| '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, |
| }; |