1

I have a use case, where I am doing an external API call from my code,
The response of the external API is required by my code further on

I am bumping into a scenario, where the external API call at times takes far too long to return a response,
casing my code to break, being a serverless function

So I want to set a time limit to the external API call,
Where if I don't get any response from it within 3 secs, I wish the code to gracefully stop the further process

Following is a pseudo-code of what I am trying to do, but couldn't figure out the logic -

let test = async () => {
    let externalCallResponse = '';

    await setTimeout(function(){ 

        //this call sometimes takes for ever to respond, but I want to limit just 3secs to it
        externalCallResponse = await externalCall();
    }, 3000);

    if(externalCallResponse != ''){
        return true;
    }
    else{
        return false;
    }
}

test();

Reference -
https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/SSM.html#getParameters-property
I'm using AWS SSM's getParameters method

Dev1ce
  • 5,390
  • 17
  • 90
  • 150

3 Answers3

1

You cannot await setTimeout as it doesn't returns a Promise.

You could implement a function that returns a Promise which is fulfilled after 3 seconds.

function timeout(seconds) {
   return new Promise((resolve) => {
       setTimeout(resolve, seconds * 1000)
   });
}

You can await the above function in your code passing the number of seconds you want to wait for

let test = async () => {
    let externalCallResponse = ''; 
    
    setTimeout(async () => {
       externalCallResponse = await externalCall();
    }, 0);

    await timeout(3); // wait for 3 seconds 

    if(externalCallResponse != '') return true;
    else return false;
}

Following code snippet demonstrates the usage of timeout function written above. It mocks a api request that returns a response after 4 seconds.

function timeout(seconds) {
  return new Promise(resolve => {
    setTimeout(resolve, seconds * 1000);
  });
}

function apiRequest() {
  return new Promise(resolve => {
    setTimeout(() => resolve('Hello World'), 4000);
  });
}

let test = async () => {
  let externalCallResponse = '';

  setTimeout(async () => {
    externalCallResponse = await apiRequest();
  }, 0);

  await timeout(3); // wait for 3 seconds

  if (externalCallResponse != '') return true;
  else return false;
};

test()
  .then(res => console.log(res))
  .catch(err => console.log(err.message));
Yousaf
  • 27,861
  • 6
  • 44
  • 69
  • What is the idea behind wrapping `externalCallResponse = await apiRequest();` with setTimeout of 0 secs? – Dev1ce Aug 04 '20 at 15:34
  • zero is the default delay so you can omit passing zero as a second argument. If you want to schedule the callback function to run after specific number of milliseconds, you can change zero to any number you want. – Yousaf Aug 04 '20 at 15:39
  • so the wrapper is like required for the timeout logic? `setTimeout(async () => { externalCallResponse = await apiRequest(); }, 0);`, I can't just do `externalCallResponse = await apiRequest();` right? Wanted to understand why that wrapping is required? – Dev1ce Aug 04 '20 at 15:43
  • 1
    if you just do `externalCallResponse = await apiRequest();` then `await timeout(3);` will not be executed until API response comes back. Wrapping API request in `setTimeout` helps us schedule the code that makes API request to run after executing `await timeout(3);`. Basically, we want to start our timeout timer before we actually make the request. – Yousaf Aug 04 '20 at 15:45
1

You could do something like this:

const timeout = async (func, millis) => {
   return new Promise(async (resolve, reject) => {
      setTimeout(() => reject(), millis);

      resolve(await func());
   });
}

timeout(() => doStuff(), 3000)
   .then(() => console.log('worked'))
   .catch(() => console.log('timed out'));

Tests:

const timeout = async (func, millis) => {
   return new Promise(async (resolve, reject) => {
      setTimeout(() => reject(), millis);

      resolve(await func());
   });
}

const doStuffShort = async () => { // Runs for 1 second
  return new Promise((resolve) => setTimeout(() => resolve(), 1000));
}

const doStuffLong = async () => { // Runs for 5 seconds
  return new Promise((resolve) => setTimeout(() => resolve(), 5000));
}

timeout(() => doStuffShort(), 3000)
   .then(() => console.log('1 worked'))
   .catch(() => console.log('1 timed out'));
   

timeout(() => doStuffLong(), 3000)
   .then(() => console.log('2 worked'))
   .catch(() => console.log('2 timed out'));
MauriceNino
  • 6,214
  • 1
  • 23
  • 60
  • 1
    Using promises inside `Promise` constructor is an [anti pattern](https://stackoverflow.com/questions/43036229/is-it-an-anti-pattern-to-use-async-await-inside-of-a-new-promise-constructor) – Yousaf Aug 04 '20 at 14:12
  • 1
    Thanks for the hint @Yousaf One could potentially rewrite it to just work with `.then()` inside the Promise and resolve from there, but I have no time right now. Good catch though – MauriceNino Aug 04 '20 at 14:17
1

you can use something like this, I created a function that return a promise then I used this promise.

let test = async () => {

    return promiseTimeout()
}

const promiseTimeout = () => {
    return new Promise(async (resolve, reject) => {
        setTimeout(function () {
            let externalCallResponse=""
            externalCallResponse = await externalCall();
            if (externalCallResponse != '') {
                return resolve(true);
            }
            else {
                return resolve(false);
            }
        }, 3000);
    })
}

test().then(result=>{
    console.log(result);
});
Abdelrahman Hussien
  • 505
  • 2
  • 4
  • 17
  • Using promises inside `Promise` constructor is an [anti pattern](https://stackoverflow.com/questions/43036229/is-it-an-anti-pattern-to-use-async-await-inside-of-a-new-promise-constructor) – Yousaf Aug 04 '20 at 14:12