Skip waiting if async function (Promise) is taking too much time

Viewed 633

In my express application, I am making call to 2 APIs. The 2nd API is managed by 3rd party and sometimes can take more than 5 seconds to respond. Hence, I want to just wait for 1 second for the API to respond. If it does not, just proceed with data from 1st API.

Below is the mock-up of the functions being called. I am thinking to use setTimeout to throw error if the API takes more than 1 second. If the API responds within 1 second then I just cancel the setTimeout and no error is ever thrown.

But there is problem with this approach:

  1. setTimeout errors cannot be catched using try...catch block.

I cannot use axios's timeout option, as I still need to wait for the 2nd API to finish the processing and save the data in the DB. This will ofcourse, can happen later, when the 2nd API call finishes.


// Function to simulate it's taking time.
async function cWait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// Track whether it took time.
let isTimeOut = false

async function test() {
  console.log('starting')
  try {
    const one = await apiCall1()
    const myt = setTimeout(() => {
      console.log('Its taking time, skip the 2nd API Call')
      isTimeOut = true
      throw new Error('Its taking time')
    })
    const two = await apiCall2(myt)
  } catch (error) {
    console.log(error)
  }
  saveInDB({ ...one, ...two })
}


async function apiCall2(timeOutInstance) {
  console.log('start-apiCall')
  await cWait(1800)
  clearTimeout(timeOutInstance)
  if (isTimeOut) saveInDB()
  console.log('done-apiCall')
}

async function apiCall1() {
  await cWait(5)
}

async function saveInDB(data) {
  console.log('saveInDB')
}

test()
2 Answers

please note, this is not the answer as it was when it was accepted as I misread the question and failed to call saveInDB in a timed out situation

Promise.race seems perfect for the job

Also, you'd actually use your cWait function, not for mock-up, but to actually do something useful ... win the race :p

const api2delay = 800;
async function cWait(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}
const TIMEDOUT = Symbol('TIMEDOUT');
async function cReject(ms) {
    return new Promise((_, reject) => setTimeout(reject, ms, TIMEDOUT));
}
function apiCall2timeout(timeoutCallback) {
    const resultApi2 = apiCall2();
    const timeout = cReject(1000);
    return Promise.race([resultApi2, timeout])
    .catch(e => {
        if (e === TIMEDOUT) {
            resultApi2.then(timeoutCallback);
        } else {
            throw e;
        }
    });
}
async function test() {
    console.log('starting')
    let one, two;
    try {
        one = await apiCall1();
        two = await apiCall2timeout(saveInDB);
    } catch (error) {
        console.log('error', error)
    }
    saveInDB({
        ...one,
        ...two
    })
}

async function apiCall2() {
    console.log('start-apiCall2')
    await cWait(api2delay)
    console.log('done-apiCall2')
    return {
        api2: 'done'
    }
}

async function apiCall1() {
    await cWait(5)
    return {
        api1: 'done'
    }
}

async function saveInDB(data) {
    console.log('saveInDB', data)
}

test()

Note: I changed where one and two were declared since const is block scoped

I you run with await cWait(800) in apiCall2, the saveInDB will run with both data. But if you run await cWait(1800), the saveInDB will run 2 times.

// Function to simulate it's taking time.
async function cWait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// https://italonascimento.github.io/applying-a-timeout-to-your-promises/
const promiseTimeout = function (ms, promise) {
  // Create a promise that rejects in <ms> milliseconds
  let timeout = new Promise((resolve, reject) => {
    let id = setTimeout(() => {
      clearTimeout(id);
      reject('Timed out in ' + ms + 'ms.')
    }, ms)
  })

  // Returns a race between our timeout and the passed in promise
  return Promise.race([
    promise,
    timeout
  ])
}

// Track whether it took time.
let isTimeOut = false

async function test() {
  console.log('starting')
  const one = await apiCall1() // get data from 1st API
  let two = {};
  try {
    two = await promiseTimeout(1000, apiCall2())
  } catch (error) {
    isTimeOut = true;
    console.log(error)
  }
  saveInDB({ ...one, ...two })
}


async function apiCall2() {
  console.log('start-apiCall')
  await cWait(800)
  console.log('done-apiCall', isTimeOut)
  if (isTimeOut) {
    saveInDB({ 2: 'two' })
  }
  return { 2: 'two' }
}

async function apiCall1() {
  await cWait(5)
  return { 1: 'one' }
}

async function saveInDB(data) {
  console.log('saveInDB', data)
}

test()
Related