function runLifecycle(lifecycle, plugins, manager, token) {
  try {
    const promise = Promise.all(
      plugins
        .filter(hasMethod(lifecycle))
        .map((plugin) => plugin[lifecycle](manager.contextFor(plugin, token))),
    );

    return raceTo(
      promise,
      MAX_LIFECYCLE_EXECUTION_TIME_IN_MS,
      `Lifecycle method ${lifecycle} took too long.`,
    );
  } catch (err) {
    return Promise.reject(err);
  }
}
Iterates through the plugins and calls the specified asynchronous lifecycle event,
returning a promise that resolves when they are all completed, or rejects if one of them
fails.

This is meant to be used on lifecycle events both at the batch level and the job level. The
passed in BatchManager is used to get the corresponding context object for the plugin/job and
is passed in as the first argument to the plugin's method.

This function is currently used for `batchStart/End` and `jobStart/End`.

@param {String} lifecycle
@param {Array<HypernovaPlugin>} plugins
@param {BatchManager} manager
@param {String} [token] - If provided, the job token to use to get the context
@returns {Promise}