Snippets - JS Promise: minimum execution time

A function that accepts a time and a promise-returning function as arguments and resolves only when the timer is up and the function resolves.

// Promise.all without failing on a rejectd promise
export function promiseAllReflect(promises = []) {  
  const reflect = promise => promise.then(
    value => ({ value, status: 'fulfilled' }),
    error => ({ error, status: 'rejected' }),
  );

  return Promise.all(promises.map(reflect));
}

export function executeAtLeast(time, func, funcArgs = []) {  
  return promiseAllReflect([
    new Promise(resolve => setTimeout(resolve, time)), 
    func(...funcArgs)
  ]);
}

Motivation

I was recently working on a React Native application where the user was to be shown a loading modal after submitting a large form request.

Initially I just opened the modal before the request and closed it upon the promise resolving:

onSubmit = async formData => {  
  openLoadingModal();
  await postData(formData);
  closeLoadingModal();
}

This is a straightforward approach and works well most of the time. However since both the form size and the user's network conditions could vary greatly, the loading modal could potentially be displayed for a very brief amount of time and appear as just a flicker.

In this situation, what I really wanted was to show the loader for at least one second so the user could clearly see that their data was being saved.

I didn't want to simply call the request on a timeout because for longer requests it would just be adding an additional second.

What I needed was to send the request and start the timer at the same time. In the event that the request finished in less than a second, we should continue waiting the full second to close the modal.

This was why I needed a minimum execution time promise. The idea behind this is to force Promise.all to resolve every promise given using a reflect in order to ensure we are always waiting for each promise.

// Promise.all without failing on a rejectd promise
export function promiseAllReflect(promises = []) {  
  const reflect = promise => promise.then(
    value => ({ value, status: 'fulfilled' }),
    error => ({ error, status: 'rejected' }),
  );

  return Promise.all(promises.map(reflect));
}

We can then use this with setTimeout to create a minimum execution time function

export function executeAtLeast(time, func, funcArgs = []) {  
  return promiseAllReflect([
    new Promise(resolve => setTimeout(resolve, time)), 
    func(...funcArgs)
  ]);
}

This gives us a function that takes a time and a function and returns both the time is up and the function has resolved.

onSubmit = async formData => {  
  openLoadingModal();
  await executeAtLeast(1000, postData(formData))
  closeLoadingModal();
}