async pageResponse(path, params = {}, placeholders = {}) {
    // Get file content
    let data;
    try {
      data = await this.readFile(path);
    } catch (e) {
      return this.notFound();
    }

    // Get config placeholders; can be an object, a function or an async function
    let configPlaceholders =
      typeof this.pagesConfig.placeholders === 'function'
        ? this.pagesConfig.placeholders(params)
        : Object.prototype.toString.call(this.pagesConfig.placeholders) === '[object Object]'
          ? this.pagesConfig.placeholders
          : {};
    if (configPlaceholders instanceof Promise) {
      configPlaceholders = await configPlaceholders;
    }

    // Fill placeholders
    const allPlaceholders = Object.assign({}, configPlaceholders, placeholders);
    const paramsAndPlaceholders = Object.assign({}, params, allPlaceholders);
    data = mustache.render(data, paramsAndPlaceholders);

    // Add placeholders in header to allow parsing for programmatic use
    // of response, instead of having to parse the HTML content.
    const headers = Object.entries(params).reduce((m, p) => {
      if (p[1] !== undefined) {
        m[`${pageParamHeaderPrefix}${p[0].toLowerCase()}`] = p[1];
      }
      return m;
    }, {});

    return { text: data, headers: headers };
  }
Creates a response with file content.
@param {String} path The path of the file to return.
@param {Object} [params={}] The parameters to be included in the response
header. These will also be used to fill placeholders.
@param {Object} [placeholders={}] The placeholders to fill in the content.
These will not be included in the response header.
@returns {Object} The Promise Router response.