user1620696
user1620696

Reputation: 11405

How to finish one operation in Node.js if some limit time is exceeded?

Consider that we want to perform some operation which takes some time to complete in Node.js, say doHeavyWork(). What we usually do is that we pass a callback function like that:

doHeavyWork(params, callbackFunction);

Now, I want to stablish one limit time for the operation to complete. What I want is that if the uptime of that operation exceeds the limit time, the function terminates and informs the callback that the limit time was exceeded.

How can this be accomplished in Node.js?

Upvotes: 2

Views: 718

Answers (1)

Mulan
Mulan

Reputation: 135406

You'd think this is possible with a simple callback, but I'm going to illustrate a problem for you

function doHeavyWork(params, callback) {
  // set the timeout
  var timeout = setTimeout(callback, 5000, Error('timeout'))
  
  // some debug message
  console.log('Please wait...')
  
  // begin heavy work
  // we sill simulate heavy work with setTimeout here
  setTimeout(function() {
    
    // if the function completed in time, clear the timeout
    clearTimeout(timeout)
    
    // callback without error
    callback(null, 'done')
  }, 3000)
}

// check to see it work
doHeavyWork('some params', function(err, res) {
  if (err)
    throw error
  else
    console.log(res)
})

Now watch what happens if you change the 3000 to a value that exceeds the 5000 ms timeout — for example, try 10000. You will see that the callback is called twice. This is not good! And not trivial to fix unless you want to clutter up your function with a ton of rubbish.

If we use a Promise, we can fix this quite easily tho

function doHeavyWork(params) {
  return new Promise(function(resolve, reject) {
    // set the timeout
    var timeout = setTimeout(reject, 5000, Error('timeout'))

    // some debug message
    console.log('Please wait...')

    // begin heavy work
    // we sill simulate heavy work with setTimeout here
    setTimeout(function() {

      // if the function completed in time, clear the timeout
      clearTimeout(timeout)

      // callback without error
      resolve('done')
    }, 3000)
  })
}

// check to see it work
doHeavyWork('some params').then(
  function(res) { console.log(res) },
  function(err) { console.log(err.message) }
)
    

Now watch what happens if you change the 3000 to 10000 again. The Promise "callback" will only be called once


Lastly, this code can even be improved just a little bit more using Promise.race. Promise.race will return a new Promise that resolves or rejects as soon as one of the input Promises resolves or rejects. This is perfect for our use case.

Most crucially, notice how the timeout logic is not coupled with doHeavyWork. This keeps your functions clean and nicely organized.

function doHeavyWork(params) {
  return new Promise(function(resolve, reject) {
    // some debug message
    console.log('Please wait...')

    // begin heavy work
    // we sill simulate heavy work with setTimeout here
    setTimeout(resolve, 3000, 'done')
  })
}

function timeout(ms) {
  return new Promise(function(resolve, reject) {
    setTimeout(reject, ms, Error('timeout'))
  })
}

// using Promise.race
Promise.race([
  timeout(5000),
  doHeavyWork('some params')
]).then(
  function(res) { console.log(res) },
  function(err) { console.log(err.message) }
)
   

In the code example above, change the timeout value of 5000 to something shorter like 1000 to see what the error looks like

Upvotes: 3

Related Questions