constructor(request, response, jobs, config) {
    const tokens = Object.keys(jobs);

    this.config = config;
    this.plugins = config.plugins;
    this.error = null;
    this.statusCode = 200;

    // An object that all of the contexts will inherit from... one per instance.
    this.baseContext = {
      request,
      response,
      batchMeta: {},
    };

    // An object that will be passed into the context for batch-level methods, but not for job-level
    // methods.
    this.batchContext = {
      tokens,
      jobs,
    };

    // A map of token => JobContext, where JobContext is an object of data that is per-job,
    // and will be passed into plugins and used for the final result.
    this.jobContexts = tokens.reduce((obj, token) => {
      const { name, data, metadata } = jobs[token];
      /* eslint no-param-reassign: 1 */
      obj[token] = {
        name,
        token,
        props: data,
        metadata,
        statusCode: 200,
        duration: null,
        html: null,
        returnMeta: {},
      };
      return obj;
    }, {});

    // Each plugin receives it's own little key-value data store that is scoped privately
    // to the plugin for the life time of the request. This is achieved simply through lexical
    // closure.
    this.pluginContexts = new Map();
    this.plugins.forEach((plugin) => {
      this.pluginContexts.set(plugin, { data: new Map() });
    });
  }
The BatchManager is a class that is instantiated once per batch, and holds a lot of the
key data needed throughout the life of the request. This ends up cleaning up some of the
management needed for plugin lifecycle, and the handling of rendering multiple jobs in a
batch.

@param {express.Request} req
@param {express.Response} res
@param {Object} jobs - a map of token => Job
@param {Object} config
@constructor