2017-09-14 19:47:35 +02:00
|
|
|
import { debug } from './debug.js';
|
2018-05-10 13:13:49 +02:00
|
|
|
import { Utils } from './utils';
|
2017-09-14 19:47:35 +02:00
|
|
|
|
2017-10-21 15:58:02 +09:00
|
|
|
/**
|
|
|
|
* @summary A list of all registered callback hooks
|
|
|
|
*/
|
|
|
|
export const CallbackHooks = [];
|
|
|
|
|
2015-05-07 18:00:23 +09:00
|
|
|
/**
|
2016-11-26 02:46:55 +08:00
|
|
|
* @summary Callback hooks provide an easy way to add extra steps to common operations.
|
2016-12-12 10:24:34 +09:00
|
|
|
* @namespace Callbacks
|
2015-05-07 18:00:23 +09:00
|
|
|
*/
|
2016-12-12 10:24:34 +09:00
|
|
|
export const Callbacks = {};
|
2015-04-23 15:42:05 +09:00
|
|
|
|
2017-10-21 15:58:02 +09:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @summary Register a callback
|
|
|
|
* @param {String} hook - The name of the hook
|
|
|
|
* @param {Function} callback - The callback function
|
|
|
|
*/
|
|
|
|
export const registerCallback = function (callback) {
|
|
|
|
CallbackHooks.push(callback);
|
|
|
|
};
|
|
|
|
|
2015-04-23 15:42:05 +09:00
|
|
|
/**
|
2016-04-09 09:41:20 +09:00
|
|
|
* @summary Add a callback function to a hook
|
2015-04-23 15:42:05 +09:00
|
|
|
* @param {String} hook - The name of the hook
|
|
|
|
* @param {Function} callback - The callback function
|
|
|
|
*/
|
2016-12-12 10:24:34 +09:00
|
|
|
export const addCallback = function (hook, callback) {
|
2015-04-23 15:42:05 +09:00
|
|
|
|
2017-04-14 11:37:30 +09:00
|
|
|
if (!callback.name) {
|
2018-01-25 15:03:03 -06:00
|
|
|
// eslint-disable-next-line no-console
|
2017-04-14 11:37:30 +09:00
|
|
|
console.log(`// Warning! You are adding an unnamed callback to ${hook}. Please use the function foo () {} syntax.`);
|
|
|
|
}
|
|
|
|
|
2015-04-23 15:42:05 +09:00
|
|
|
// if callback array doesn't exist yet, initialize it
|
2016-12-12 10:24:34 +09:00
|
|
|
if (typeof Callbacks[hook] === "undefined") {
|
|
|
|
Callbacks[hook] = [];
|
2015-04-23 15:42:05 +09:00
|
|
|
}
|
|
|
|
|
2016-12-12 10:24:34 +09:00
|
|
|
Callbacks[hook].push(callback);
|
2015-05-01 18:22:00 +02:00
|
|
|
};
|
2015-04-23 15:42:05 +09:00
|
|
|
|
2015-04-27 10:10:52 +09:00
|
|
|
/**
|
2016-04-09 09:41:20 +09:00
|
|
|
* @summary Remove a callback from a hook
|
2015-04-27 10:10:52 +09:00
|
|
|
* @param {string} hook - The name of the hook
|
|
|
|
* @param {string} functionName - The name of the function to remove
|
|
|
|
*/
|
2016-12-12 10:24:34 +09:00
|
|
|
export const removeCallback = function (hookName, callbackName) {
|
|
|
|
Callbacks[hookName] = _.reject(Callbacks[hookName], function (callback) {
|
2015-05-10 14:36:47 +09:00
|
|
|
return callback.name === callbackName;
|
2015-04-27 10:10:52 +09:00
|
|
|
});
|
2015-05-01 18:22:00 +02:00
|
|
|
};
|
2015-04-27 10:10:52 +09:00
|
|
|
|
2015-04-23 15:42:05 +09:00
|
|
|
/**
|
2016-04-09 09:41:20 +09:00
|
|
|
* @summary Successively run all of a hook's callbacks on an item
|
2016-04-14 15:54:23 +09:00
|
|
|
* @param {String} hook - First argument: the name of the hook
|
2018-05-14 20:26:40 +02:00
|
|
|
* @param {Any|Promise<Any>} item - Second argument: the post, comment, modifier, etc. on which to run the callbacks
|
2016-04-14 15:54:23 +09:00
|
|
|
* @param {Any} args - Other arguments will be passed to each successive iteration
|
2018-05-14 20:26:40 +02:00
|
|
|
* @returns {Any|Promise<Any>} Returns the item after it's been through all the callbacks for this hook
|
2015-04-23 15:42:05 +09:00
|
|
|
*/
|
2016-12-12 10:24:34 +09:00
|
|
|
export const runCallbacks = function () {
|
2015-04-23 15:42:05 +09:00
|
|
|
|
2017-05-30 09:49:38 +09:00
|
|
|
// the first argument is the name of the hook or an array of functions
|
2016-04-14 15:54:23 +09:00
|
|
|
const hook = arguments[0];
|
2018-05-14 20:24:12 +02:00
|
|
|
// find registered hook
|
|
|
|
const registeredHook = CallbackHooks.find(({ name }) => name === hook);
|
|
|
|
// the second argument is the item on which to iterate; wrap it with promise if hook is async
|
|
|
|
const item = registeredHook && registeredHook.runs === 'async'
|
|
|
|
? Promise.resolve(arguments[1])
|
|
|
|
: arguments[1];
|
2016-04-14 15:54:23 +09:00
|
|
|
// successive arguments are passed to each iteration
|
|
|
|
const args = Array.prototype.slice.call(arguments).slice(2);
|
2018-05-10 13:13:49 +02:00
|
|
|
// flag used to detect the callback that initiated the async context
|
2018-05-14 20:24:12 +02:00
|
|
|
let asyncContext = Utils.isPromise(item);
|
2016-04-14 15:54:23 +09:00
|
|
|
|
2017-05-30 09:49:38 +09:00
|
|
|
const callbacks = Array.isArray(hook) ? hook : Callbacks[hook];
|
2015-04-23 15:42:05 +09:00
|
|
|
|
|
|
|
if (typeof callbacks !== "undefined" && !!callbacks.length) { // if the hook exists, and contains callbacks to run
|
|
|
|
|
2018-05-10 13:13:49 +02:00
|
|
|
const runCallback = (accumulator, callback) => {
|
2018-01-06 13:40:40 +01:00
|
|
|
debug(`\x1b[32m>> Running callback [${callback.name}] on hook [${hook}]\x1b[0m`);
|
2017-05-14 10:42:19 +09:00
|
|
|
const newArguments = [accumulator].concat(args);
|
2018-01-06 13:40:40 +01:00
|
|
|
|
2017-05-14 10:42:19 +09:00
|
|
|
try {
|
2017-05-23 09:46:34 +09:00
|
|
|
const result = callback.apply(this, newArguments);
|
|
|
|
|
2017-08-21 15:35:19 +09:00
|
|
|
// if callback is only supposed to run once, remove it
|
|
|
|
if (callback.runOnce) {
|
|
|
|
removeCallback(hook, callback.name);
|
|
|
|
}
|
|
|
|
|
2017-05-23 09:46:34 +09:00
|
|
|
if (typeof result === 'undefined') {
|
|
|
|
// if result of current iteration is undefined, don't pass it on
|
2017-12-27 09:55:06 +09:00
|
|
|
// debug(`// Warning: Sync callback [${callback.name}] in hook [${hook}] didn't return a result!`)
|
2018-05-10 13:13:49 +02:00
|
|
|
return accumulator;
|
2018-05-14 20:25:46 +02:00
|
|
|
} else if (Utils.isPromise(result)) {
|
|
|
|
if (registeredHook && registeredHook.runs === 'sync') {
|
|
|
|
console.log(`// Warning! Async callback [${callback.name}] executed in sync hook [${hook}], its value is being skipped.`);
|
|
|
|
return accumulator;
|
2018-05-14 20:26:19 +02:00
|
|
|
} else if (!asyncContext) {
|
|
|
|
debug(`\x1b[32m>> Started async context in hook [${hook}] by [${callback.name}]\x1b[0m`);
|
|
|
|
asyncContext = true;
|
2018-05-14 20:25:46 +02:00
|
|
|
}
|
2017-05-23 09:46:34 +09:00
|
|
|
}
|
2018-05-14 20:25:46 +02:00
|
|
|
return result;
|
2017-05-14 10:42:19 +09:00
|
|
|
} catch (error) {
|
2018-01-25 15:03:03 -06:00
|
|
|
// eslint-disable-next-line no-console
|
2018-01-06 13:40:40 +01:00
|
|
|
console.log(`\x1b[31m// error at callback [${callback.name}] in hook [${hook}]\x1b[0m`);
|
2018-01-25 15:03:03 -06:00
|
|
|
// eslint-disable-next-line no-console
|
2017-05-14 10:42:19 +09:00
|
|
|
console.log(error);
|
2017-07-07 10:21:15 +09:00
|
|
|
if (error.break || error.data && error.data.break) {
|
2017-07-07 09:50:05 +09:00
|
|
|
throw error;
|
|
|
|
}
|
2017-05-23 09:46:34 +09:00
|
|
|
// pass the unchanged accumulator to the next iteration of the loop
|
2017-05-14 10:42:19 +09:00
|
|
|
return accumulator;
|
|
|
|
}
|
2018-05-10 13:13:49 +02:00
|
|
|
};
|
|
|
|
|
2018-05-14 20:26:19 +02:00
|
|
|
return callbacks.reduce(function (accumulator, callback) {
|
2018-05-10 13:13:49 +02:00
|
|
|
if (Utils.isPromise(accumulator)) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
accumulator
|
|
|
|
.then(result => {
|
|
|
|
try {
|
|
|
|
// run this callback once we have the previous value
|
|
|
|
resolve(runCallback(result, callback));
|
|
|
|
} catch (error) {
|
|
|
|
// error will be thrown only for breaking errors, so throw it up in the promise chain
|
|
|
|
reject(error);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch(reject);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
return runCallback(accumulator, callback);
|
|
|
|
}
|
2015-05-04 10:19:50 +09:00
|
|
|
}, item);
|
2015-05-01 18:22:00 +02:00
|
|
|
|
2015-05-04 10:19:50 +09:00
|
|
|
} else { // else, just return the item unchanged
|
|
|
|
return item;
|
|
|
|
}
|
|
|
|
};
|
2015-04-23 15:42:05 +09:00
|
|
|
|
2015-05-04 10:19:50 +09:00
|
|
|
/**
|
2018-05-14 20:28:47 +02:00
|
|
|
* @summary Run all of a hook's callbacks on an item in parallel (only works on server)
|
2016-04-14 15:54:23 +09:00
|
|
|
* @param {String} hook - First argument: the name of the hook
|
2018-05-14 20:28:47 +02:00
|
|
|
* @param {Any} args - Other arguments will be passed to each callback
|
|
|
|
* @return {Promise<Object>} Callbacks results keyed by callbacks names
|
2015-05-04 10:19:50 +09:00
|
|
|
*/
|
2018-05-14 20:28:47 +02:00
|
|
|
export const runCallbacksAsync = async function() {
|
2016-01-03 18:18:09 +01:00
|
|
|
|
2017-05-30 09:49:38 +09:00
|
|
|
// the first argument is the name of the hook or an array of functions
|
2016-01-02 18:41:45 +01:00
|
|
|
var hook = arguments[0];
|
2016-02-24 18:11:53 +09:00
|
|
|
// successive arguments are passed to each iteration
|
2016-01-02 18:41:45 +01:00
|
|
|
var args = Array.prototype.slice.call(arguments).slice(1);
|
2017-05-30 09:49:38 +09:00
|
|
|
|
|
|
|
const callbacks = Array.isArray(hook) ? hook : Callbacks[hook];
|
2015-04-23 15:42:05 +09:00
|
|
|
|
2015-05-04 10:19:50 +09:00
|
|
|
if (Meteor.isServer && typeof callbacks !== "undefined" && !!callbacks.length) {
|
2015-04-23 15:42:05 +09:00
|
|
|
|
2018-05-14 20:28:47 +02:00
|
|
|
const results = await Promise.all(
|
|
|
|
callbacks.map(callback => {
|
2018-01-06 13:40:40 +01:00
|
|
|
debug(`\x1b[32m>> Running async callback [${callback.name}] on hook [${hook}]\x1b[0m`);
|
2018-05-14 20:28:47 +02:00
|
|
|
return callback.apply(this, args);
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
return results.reduce(
|
|
|
|
(accumulator, result, index) => ({ ...accumulator, [callbacks[index].name]: result }),
|
|
|
|
{},
|
|
|
|
);
|
2015-04-23 15:42:05 +09:00
|
|
|
}
|
2016-01-03 18:18:09 +01:00
|
|
|
|
2016-12-13 11:32:23 +09:00
|
|
|
};
|