docs: function finaliseBatch(batch, processOutcomes, cancellable, context) {
    const task = this;
  
    const freezeProcessingTasksOutcome = freezeProcessingTasks(batch, context);
  
    function discard() {
      const discardTask = task.getSubTask(discardAnyRejectedMessages.name);
      const p = discardTask.execute(batch, cancellable, context);
      return whenDone(discardTask, p, cancellable, context);
    }
  
    // Discard any finalised messages that contain at least one rejected task
    const discardRejectedMessagesPromise = discard();
  
    // Freeze any finalising tasks after discarding (or at least attempting to discard) any rejected messages
    const freezeFinalisingTasksPromise = discardRejectedMessagesPromise
      .then(() => freezeFinalisingTasks(batch, context));
  
    function save() {
      const saveTask = task.getSubTask(saveBatchState.name);
      const p = saveTask.execute(batch, context);
      return whenDone(saveTask, p, cancellable, context);
    }
  
    // Save the current state of the batch after discarding (or at least attempting to discard) any rejected messages
    const saveBatchStatePromise = freezeFinalisingTasksPromise.then(save);
  
    // Create a finalised promise that will ONLY complete when every one of the other finalising promises resolve
    const finalisingPromise = Promises.every([freezeProcessingTasksOutcome, discardRejectedMessagesPromise,
      freezeFinalisingTasksPromise, saveBatchStatePromise], cancellable, context);
  
    const postFinaliseBatchTask = task.getSubTask(postFinaliseBatch.name);
  
    // Set a timeout to trigger at or within the last second, which will hopefully give us a enough time to
    // complete the finalisation of the batch before the Lambda runs out of time to execute
    const timeoutCancellable = {};
    // Use the greater of the configured or minimum finalise timeout at percentage
    const timeoutAtPercentage = Math.max(context.streamProcessing.timeoutAtPercentageOfRemainingTime,
      MIN_FINALISE_TIMEOUT_AT_PERCENTAGE);
    const timeoutAtLastSec = Math.max(context.awsContext.getRemainingTimeInMillis() - 1000, 0);
    const timeoutMs = Math.max(timeoutAtLastSec, calculateTimeoutMs(timeoutAtPercentage, context));
  
    context.debug(`Creating a finalise batch timeout to trigger after ${timeoutMs} ms (remaining time: ${context.awsContext.getRemainingTimeInMillis()} ms)`);
  
    const timeoutPromise = createTimeoutPromise(task, timeoutMs, timeoutCancellable, cancellable, context).then(
      timeoutTriggered => {
        if (timeoutTriggered) { // If the timeout triggered then
          // timeout any and all of the finalising tasks (reusing the timeout error set on the finalise batch task by createTimeoutPromise if any)
          const timeoutError = isInstanceOf(task.error, TimeoutError) ? task.error :
            new TimeoutError(`Ran out of time to complete ${task.name}`);
          batch.timeoutFinalisingTasks(timeoutError);
          return Promise.reject(timeoutError);
          // return new Failure(timeoutError);
        }
        return timeoutTriggered;
      }
    );
    ignoreUnhandledRejection(timeoutPromise);
  
    const completedPromise = createCompletedPromise(task, finalisingPromise, batch, timeoutCancellable, context);
    ignoreUnhandledRejection(completedPromise);
  
    const completedVsTimeoutPromise = Promise.race([completedPromise, timeoutPromise]);
  
    // Whichever finishes first, wrap up as best as possible
    return completedVsTimeoutPromise.then(
      outcomes => {
        const progress = batch.assessProgress();
  
        // Find the first finalising failure (if any) to use as the error with which to fail this Lambda
        const finaliseFailure = Try.findFailure(outcomes);
  
        // Throw an error to trigger replay of the entire batch if finalising failed
        if (finaliseFailure) {
          const finaliseError = finaliseFailure.error;
          context.error(`Triggering a replay of the entire batch with ${progress}, due to finalising failure`, finaliseError);
          throw finaliseError;
        }
  
        // Throw an error to trigger replay of the batch if the batch is NOT fully finalised yet (& sequencing is required)
        if (!batch.isFullyFinalised()) {
          // Find the first FinalisedError failure amongst the processing failures (if any)
  
          const finalisedFailure = (Array.isArray(processOutcomes) ? Arrays.flatten(processOutcomes) : [processOutcomes])
            .find(o => o && o.isFailure && o.isFailure() && isInstanceOf(o.error, FinalisedError));
  
          if (finalisedFailure) {
            // Convert the FinalisedError into a FatalError
            const fatalError = new FatalError(`FATAL - ${finalisedFailure.error.message}`, finalisedFailure.error);
            context.error(fatalError);
            throw fatalError;
          }
  
          // Find the first processing failure (if any)
          const processFailure = Try.findFailure(processOutcomes);
  
          // Log the previous processing failure (if any) as part of the reason for replaying the entire batch
          if (processFailure) {
            const processError = processFailure.error;
            context.error(`Triggering a replay of the entire batch with ${progress}, due to processing failure`, processError);
            throw processError;
          }
  
          const incompleteError = new Error(`Triggering a replay of the entire batch with ${progress}, since it is still incomplete!`);
          context.error(incompleteError);
          throw incompleteError;
        }
  
        if (Array.isArray(outcomes)) {
          // Finalisation must have completed
          // Batch is finally fully finalised & there was no finalising error, so batch is complete!
          context.info(`FINAL STATE: Batch (${batch.shardOrEventID}) with ${progress} is complete & fully finalised`);
          return batch;
  
        } else {
          // Finalisation probably timed out
          const error = task.error ? task.error : new Error(`Unexpected finalising outcome: ${stringify(outcomes)}`);
          context.error(`Triggering a replay of the entire batch with ${progress}, due to unexpected outcome`, error);
          throw error;
        }
      },
      err => {
        // Finalisation must have failed or timed out
        const progress = batch.assessProgress();
  
        // // Terminate the finalise batch task with EITHER the process phase's first Failure outcome (if any) ...
        // Try.flatten(processOutcomes);
        // // ... OR throw the finalise error
        // throw err;
  
        context.error(`Triggering a replay of the entire batch with ${progress}, due to finalise batch error`, err);
        throw err;
      })
      .then(
        batch => {
          // Post-finalise the batch after a successful finalise ...
          return postFinaliseBatchTask.execute(batch, context)
            .then(() => batch, () => batch); // ... and then return the finalise results
        },
        err => {
          // Post-finalise the batch after a failed finalise ...
          return postFinaliseBatchTask.execute(batch, context)
            .then(() => Promise.reject(err), () => Promise.reject(err)); // ... and then return the finalise error
        })
      .then(
        batch => {
          // Log the final results after a successful finalise ...
          logFinalResults(batch, undefined, context);
          return batch; // ... and then return the batch
        },
        err => {
          // Log the final results after a failed finalise ...
          logFinalResults(batch, err, context);
          throw err; // ... and then return the finalise error
        })
      .catch(err => {
        // // Terminate the finalise batch task with EITHER the process phase's first Failure outcome (if any) ...
        // Try.flatten(processOutcomes);
        // // ... OR throw the finalise error
        // throw err;
  
        // // Terminate the finalise batch task with EITHER the process phase's first Failure outcome (if any) ...
        // // Try.flatten(processOutcomes);
  
        return Promise.reject(err);
      });
  }