Home > Enterprise >  Preventing simultaneous calls to the same Promise / async function
Preventing simultaneous calls to the same Promise / async function

Time:01-16

The Situation

I have an asynchronous function that takes ~X seconds to resolve. The duration depends on network latency so it is unpredictable. The function is triggered by user input (think "save current form values to the server")

I would like to prevent this function from running multiple times simultaneously.

I would also like to "queue" calls to the function for potentially calling when the time is right.

The Question

(A) Is there a term for this concept of a "patient" async function.

(B) Is there a tool (similar to debounce) that will take an async function and turn it into a "patient" one.

CodePudding user response:

This answer is based on the answer by @danh.

The function queuePromise takes a function that returns a promise. Whenever the queued function is called, it's promise is chained to the promise of the previous call.

The main difference between @danh's answer and this, is that the queued function can receive arguments, and the promise is a simple let variable.

const queuePromise = fn => {
  let pending = Promise.resolve()
  return (...args) => {
    const func = () => fn(...args)
  
    return pending = pending.then(func, func)
  }

}

const demo = name => id => {
  console.log(`started ${name} ${id}`)
  
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // example of a failed promise in the middle of the chain
      if(id === 2) reject(`failed ${name} ${id}`)
      else resolve(`ended ${name} ${id}`)
    }, 500)
  })
}

const patientFoo = queuePromise(demo('foo'))
patientFoo(1).then(console.log).catch(console.log)
patientFoo(2).then(console.log).catch(console.log)
patientFoo(3).then(console.log).catch(console.log)

const patientBar = queuePromise(demo('bar'))
patientBar(1).then(console.log).catch(console.log)
patientBar(2).then(console.log).catch(console.log)
patientBar(3).then(console.log).catch(console.log)

CodePudding user response:

Below, makePatient wraps a given function in another that invokes it only after the chain of prior invocations is resolved.

function pause() {
  return new Promise(resolve => {
    setTimeout(resolve, 1000)
  })
}

function foo(str) {
  return pause().then(() => {
    if (str !== 'foo') throw 'what the foo?';
    console.log(str);
  });
}

function bar(str) {
  return pause().then(() => console.log(str));
}

function makePatient(fn) {
  let pending = Promise.resolve();

  return (...params) => {
    return pending = pending.then(() => fn(...params)).catch(console.log);
  }
}

let patientFoo = makePatient(foo);
let patientBar = makePatient(bar);

patientFoo('foo');
patientFoo('not foo').catch(e => console.log)
patientFoo('foo');
patientFoo('foo');

patientBar('bar');
patientBar('bar');
patientBar('bar').finally(() => console.log("all the bars are closed"));

  •  Tags:  
  • Related