Async Operations in Javascript

Callbacks, promises, async/await

function myOperation(callback){
  // do stuff 
  callback({message: 'hello'});
}


myOperation(function (data) {
  console.log(data.message)
})

console.log('hello2')

1

2

3

-> hello2

-> hello

Callback problems

function myOperation(callback){
  // do stuff 
  if(condition){
    callback({message: 'result 1'});
  }
  if(otherCondition) {
    callback({message: 'result 2'});
  }
}


myOperation(function (data) {
  console.log(data.message)
})

Can be called more than once

-> result 1

-> result 2

No guarantee of calling

function myOperation(callback){
  // do stuff 
  if(condition){
    // developer forgot to call callback here
  } else {
    callback({message: 'result'})
  }
}


myOperation(function (data) {
  console.log(data.message)
})

will never be executed

when "condition" is true

No guarantee of calling with parameters in the right order

function myOperation(callback){
  // do stuff 
  if(condition){
    callback(new Error('Oops. Something wrong happened'))
  } else {
    callback({message: 'result'})
  }
}


myOperation(function (err, data) {
  console.log(err)
})

condition == true ->

Error: Oops. Something wrong happened

condition == false ->

{message: "result"}

Callback Hell

function saveUser(callback){
  // do stuff
  callback({user: {id: 124}})
}

function sendRegistrationEmail(userId, callback){
  // do stuff
  callback({success: true})
}

function saveAnalytics(userId, callback){
  // do stuff
  callback({success: true})
}

saveUser(function (data) {
  const userId = data.userId
  sendRegistrationEmail(userId, function(result){
    if(result.success){
      saveAnalytics(userId, function(analyticsResult) {
        if(analyticsResult.success){
          // We finally completed our operations
        }
      })
    }
  })
})

Parallel callbacks

let results = [];
let completed = false;

operation1(function (result1){
  results.push(result1)
  if(completed){
    onComplete()
  }
})

operation2(function (result2){
  results.push(result2)
  if(completed){
    onComplete()
  }
})

function onComplete(){
  console.log(results)
}

array with 2 results

Promises

A Promise can have 3 states

  • pending: initial state, operation in progress
  • fulfilled: operation completed successfully
  • rejected: operation failed

A Promise is a utility built on top of callbacks which aims to fix callback related problems

function operation(){
    return new Promise((resolve, reject) => {
        resolve({message: 'operation result'})
    })
}


operation()
.then((result) => {
  console.log(result.message);
})

console.log('Hello')

Promise example

-> operation result

1

2

3

Promise anatomy

operation()
   .then((result)=> {
     
   })
   .catch((err) => {
     
   })
   .finally(() => {
   
   })

-> pending

-> fulfilled

-> rejected

-> either fulfilled or rejected

Promise chaining

operation()
   .then((result)=> {
     return operation2(result);
   })
   .then((result2) => {
     
   })
   .catch((err) => {
   
   })

1

2

3

Parallel promises

function operation1(){
    return new Promise((resolve, reject) => {
        resolve({message: 'mesaj 1'})
    })
}

function operation2(){
    return new Promise((resolve, reject) => {
        resolve({message: 'mesaj 2'})
    })
}

Promise.all([operation1(), operation2()])
       .then((results) => {
          console.log(results)
        })

Promise properties

function operation(){
    return new Promise((resolve, reject) => {
        resolve({message: 'message 1'})
        resolve({message: 'message 2'})
    })
}

Resolve or reject can be called only ONCE and has 0 or 1 parameters

Promise advantages

  • more robust compared to callbacks
  • makes sequential, parallel operations easier to write and understand
  • enforces some rules which makes the code more robust

Async/await

Language utilities built on top of promises.

Supported in:

  •  Node.js > 7.6
  • All browsers except Internet Explorer 
function operation(){
 return new Promise((resolve, reject)=>{
    resolve({message: 'result'})
 })
}


operation()
.then((result) => {
  console.log(result.message)
})

console.log('New message')
async function operation(){
  return {message: 'result'}
}


let result = await operation()

console.log(result.message)

console.log('New message')

1

2

3

1

2

3

Async/await example

Async/await code execution is different!
Be aware of this especially when trying to do parallel operations

Sequential operations

let user = await saveUser(userData);

let emailResult = await sendRegistrationEmail(user);

await saveAnalyticsData()
try {
  let user = await saveUser(userData);

  let emailResult = await sendRegistrationEmail(user);

  await saveAnalyticsData()

} catch (err) {
  // error handling is done here
} finally {
  // always executed
}

Error handling

Parallel operations

try {
 
  let results = await Promise.all([operation1(), operation2()])
 
} catch(err){

  // error handling here
}

async/await advantages

  • simpler code sequential looking code
  • very easy to do sequential operations
  • executes slightly different compared to promises
  • can be confusing at the beginning

gotchas

Async Javascript Operations

By Cristi Jora

Async Javascript Operations

  • 860