1

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()
Adarsh Madrecha
  • 6,364
  • 11
  • 69
  • 117

2 Answers2

2

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

Jaromanda X
  • 53,868
  • 5
  • 73
  • 87
  • This is perfect and simplified. Have upvoted and accepted the answer. – Adarsh Madrecha May 11 '21 at 14:52
  • Any suggestion on how to let `apiCall2` function know it was timed out? So that `saveInDB` is not called twice? – Adarsh Madrecha May 11 '21 at 14:55
  • is it called twice? not as far as I can see - it's called twice in the other answer, not this one – Jaromanda X May 11 '21 at 14:59
  • You were not calling the `saveInDB` function from `apiCall2`. So, can there be a way to inform `apiCall2` that it was timed out? Bass which I can then call the `saveInDB` function in `apiCall2` too. – Adarsh Madrecha May 11 '21 at 15:03
  • as far as "letting apiCall2 know" - what's the purpose of letting apiCall2 know it's taken too long? what do you want apiCall2 to do if it has timed out? Of course there's multiple ways to do that, but the questions above are important – Jaromanda X May 11 '21 at 15:03
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/232235/discussion-between-adarsh-madrecha-and-jaromanda-x). – Adarsh Madrecha May 11 '21 at 15:04
  • no, I wasn't calling that, because you said "If it does not, just proceed with data from 1st API" - but then I now see you want to save separately if there is a timeout - sorry for not reading the question properly ... very easy fix – Jaromanda X May 11 '21 at 15:05
  • @AdarshMadrecha - done - though ... I don't really like mixing .then with async/await :( – Jaromanda X May 11 '21 at 15:09
  • to be honest, I think the other answer is better for your case - I misread the question (didn't read the very last part) – Jaromanda X May 11 '21 at 15:14
  • 1
    @AdarshMadrecha - I've rewritten it - but it's a little more like the other answer now - but I think this code is a little sturdier - note the use of the `.catch` rather that try/catch await/sync in the `apiCall2timeout` function - I believe there are some patterns that are just simpler without async/await try/catch - just because async/await exists, doesn't mean it's **always** better code - I tried with async/await in that function, and the code looks like garbage :p – Jaromanda X May 11 '21 at 15:40
1

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()
Pankaj Kumar
  • 198
  • 8