Skip to content
davidbarna edited this page Apr 27, 2018 · 3 revisions

Async programming

Asynchrony

It’s easier to understand "asynchrony" if you first understand what “synchrony”, the opposite, means.

Synchronous code

Synchronous code as “a bunch of statements in sequence”; so each statement in your code is executed one after the other.

console.log('First')
console.log('Second')
console.log('Third')
// "First" "Second" "Third"

Asynchronous code

Asynchronous code takes statements outside of the main program flow, allowing the code after the asynchronous call to be executed immediately without waiting.

Let's see it in action

Consider:

var users = jQuery
  .get('//jsonplaceholder.typicode.com/users')
  .done(function(response) {
    console.log('first log: ' + response.length)
  })
console.log('second log: ' + users.length)

Output:

"second log: undefined"
"first log: 10"
  • Logs are not made by order of code lines
  • The result of getUsers is not an array of users

Code executed now:

var users = jQuery
  .get('//jsonplaceholder.typicode.com/users')
  .done(/* callback */)
console.log('second log: ' + users.length)

Code executed later:

function(response){
  console.log('first log: ' + response.length)
}

This is a callback that will be executed only when jsonplaceholder.typicode.com responds with data.

Asynchrony is essential for activities that are potentially blocking.

While browser is waiting for a response from the web service, code execution is not blocked and keeps executing the rest of the lines.

Going further

Consider:

function log(content) {
  console.log(content)
}
function printing() {
  log(1)
  setTimeout(function callback1() {
    log(2)
  }, 0)
  setTimeout(function callback2() {
    log(3)
  }, 1000)
  log(4)
}
printing()

Output:

1
4
2
3

Why is 3 after 4 if the timeout has no time to wait?

To get it, we need to dive into runtime concepts.

Javascript engine is composed of:

  • Stack: Function calls form a stack of frames.

  • Heap: Objects are allocated in a heap which is just a name to denote a large mostly unstructured region of memory.

  • Queue: A JavaScript runtime contains a message queue, which is a list of messages to be processed. A function is associated with each message. When the stack is empty, a message is taken out of the queue and processed.

runtime Note: source here: http://liberablogo.com/wp-content/uploads/2017/01/chrome.png

Event loop

The event loop got its name because of how it's usually implemented, which usually resembles:

while (queue.waitForMessage()) {
  queue.processNextMessage()
}

queue.waitForMessage waits synchronously for a message to arrive if there is none currently.

Check the docs

Let's how it works with our code

function log(content) {
  console.log(content)
}
function printing() {
  log(1)
  setTimeout(function callback1() {
    log(2)
  }, 0)
  setTimeout(function callback2() {
    log(3)
  }, 1000)
  log(4)
}
printing()

http://latentflip.com/loupe/

Making the synchronous, asynchronous

Sometimes, synchronous heavy tasks blocks runtime until the end.

It can really damage user's experience.

But now that you understand asynchony in javascript, you can take advantage of it...

Consider:

function times(count, func, callback) {
  if (!count) {
    callback()
  } else {
    func(count)
    times(--count, func, callback)
  }
}

https://stackblitz.com/github/we-learn-js/js-training-code/tree/master/src/AsyncProgramming/xohecax-3?embed

http://latentflip.com/loupe/

Make it asynchronous, non blocking:

function times(count, func, callback) {
  if (!count) {
    callback()
  } else {
    func(count)
    setTimeout(function posponedTimes() {
      times(--count, func, callback)
    }, 0)
  }
}

https://stackblitz.com/github/we-learn-js/js-training-code/tree/master/src/AsyncProgramming/kazequs?embed

http://latentflip.com/loupe/

Consider:

function traverseRecursion(current, depth) {
  var children = current.childNodes
  for (var i = 0, len = children.length; i < len; i++) {
    traverseRecursion(children[i], depth + 1)
  }
}

Make it asynchronous, non blocking:

function traverseRecursion(current, depth) {
  var children = current.childNodes
  for (var i = 0, len = children.length; i < len; i++) {
    setTimeout(
      (function(current, depth) {
        traverseRecursion(current, depth)
      })(children[i], depth + 1),
      0
    )
  }
}

https://stackblitz.com/github/we-learn-js/js-training-code/tree/master/src/AsyncProgramming/fupuveh-9?embed

Callbacks

Callbacks are the fundamental unit of asynchrony in JS.

Instead of immediately returning some result like most functions, functions that use callbacks take some time to produce a result.

Practice

Implement getUsersPhotos() to retrieve photos of all users.

var getUsers = function (callback, limit) { ... }
var getUserAlbums = function (userId, callback, limit) { ... }
var getAlbumPhotos = function (albumId, callback, limit) { ... }

function getUsersPhotos(callback, limit) {
  // YOUR CODE GOES HERE
}

https://stackblitz.com/github/we-learn-js/js-training-code/tree/master/src/AsyncProgramming/rojomaf?embed

You've probably ended up with that kind of code

function getUsersPhotos(callback, limit) {
  var result = []
  var albumsLeftToProcess = 0
  getUsers(function(users) {
    users.forEach(function(user) {
      getUserAlbums(
        user.id,
        function(albums) {
          albums.forEach(function(album) {
            albumsLeftToProcess++
            getAlbumPhotos(
              album.id,
              function(photos) {
                photos.forEach(function(photo) {
                  result.push(photo)
                })
                --albumsLeftToProcess || callback(result)
              },
              limit
            )
          })
        },
        limit
      )
    })
  }, limit)
}

Note: We call that the "Pyramid of Doom"

https://stackblitz.com/github/we-learn-js/js-training-code/tree/master/src/AsyncProgramming/tevulew?embed

The problem with callbacks

Callback hell | Pyramid of doom

The cause of callback hell is when people try to write JavaScript in a way where execution happens visually from top to bottom.

http://callbackhell.com/

Inversion of control

Callbacks suffer from inversion of control in that they implicitly give control over to another party.

This control transfer leads us to a troubling list of trust issues, such as whether the callback is called more times than we expect.

Promises

Promises are now the official way to provide async return values in both JavaScript and the DOM.

The Promise object is used for asynchronous computations. A promise represents a value which may be available now, or in the future, or never.

Check the docs

Basics

Let's see how it looks like:

var promise = getUsers()
promise.then(
  function(users) {
    console.log(users)
  },
  function(e) {
    console.error(e.message)
  }
)

Note: getUsers returns a promise. Promise always provides functions then and catch. then handler will be called with the value if the promise is resolved catch handler will be called with the value if the promise is rejected explicitly or any error occurs during the execution

States of a promise

  • pending - Hasn't resolved or rejected yet
  • settled - Has resolved or rejected
    • resolved - The action relating to the promise succeeded
    • rejected - The action relating to the promise failed
var promise = getUsers() // Promise is pending
promise.then(
  function(users) {
    // Promise is resolved and settled
    console.log(users)
  },
  function(e) {
    // Promise is rejected and settled
    console.error(e.message)
  }
)

Immutability of settled promises

  • A promise is resolved with a value, passed to resolution handler

  • A promise is rejected for a reason or a thrown exception, passed to the rejection handler

  • Once a promise is settled (resolved or rejected), it's immutable and can't be resolved with a different value or rejected afterwards

Check full specs

Absence of race conditions

Race condition: output is dependent on the sequence or timing of other uncontrollable events.

If the promise is settled when a corresponding handler is attached, the handler will be called.

Then, so there is no race condition between an asynchronous operation completing and its handlers being attached.

http get with callbacks

function reqListener() {
  console.log(this.responseText)
}

var oReq = new XMLHttpRequest()
oReq.addEventListener('load', reqListener)
oReq.open('GET', 'http://www.example.org/example.txt')
oReq.send()

If load handler is attached after the response, the handler will not be executed

http get with promises

function reqListener(response) {
  console.log(response.text())
}

var oReq = fetch('flowers.jpg')

oReq.then(reqListener)

If then handler is attached after the response, the handler will be executed

Consuming Promises

Promise.prototype.then( onResolve, onReject )

romise.then() accepts both resolution and rejection handlers

getUsers() // returns a promise
  .then(
    function(users) {
      console.log(users)
    },
    function(e) {
      console.error(e.message)
    }
  )
Resolution handling

If the promise returned by getUsers is resolved, resolution handler will be called with the value.

getUsers().then(
  function(users) {
    console.log(users)
  },
  function(e) {
    console.error(e.message)
  }
)
Rejection handling

If the promise returned by getUsers is rejected, rejection handler will be called with a reason or Error.

getUsers().then(
  function(users) {
    console.log(users)
  },
  function(e) {
    console.error(e.message)
  }
)
Chaining

promise.then always returns a new promise.

The returned value in the attached handler will resolved a newly created promise.

getUsers()
  // promise 1
  .then(function(users) {
    return users.filter(user => !!user.active)
  })
  // promise 2
  .then(function(activeUsers) {
    console.log(activeUsers)
  })
// promise 3
Rejection cascade

As each then returns a new independant promise, the rejection handler is only triggered if something happens is the "origin" promise.

Also, the resolution handler will not be called on the next promise if first promise is rejected

getUsers() // promise 1
  .then(
    function onResolve1(users) {
      throw new Error('No users')
      return users.filter(user => !!user.active)
    },
    function onReject1(e) {
      console.error(e)
    }
  ) // promise 2
  .then(
    function onResolve2(activeUsers) {
      console.log(activeUsers)
    },
    function onReject2(e) {
      console.error(e)
    }
  ) // promise 3

Only onResolve1 and onRejection2 will be called

Casting

In a resolution handler, you can either return a plain value or a new promise

getUsers()
  // promise 1, of getUsersPhotos
  .then(function(users) {
    return users.filter(user => !!user.active)
  })
  // promise 2, of active users
  .then(function(activeUsers) {
    return getAlbums(activeUsers[0].id)
    // promise 3, of first user's albums
  })
  // promise 3, of first user's albums
  .then(function(firstActiveUserAlbums) {
    console.log(firstActiveUserAlbums)
  })
// promise 4, of undefined (nothing was returned)

Creating Promises

The Promise object

new Promise(function(resolve, reject) {
  /* code */
})

In ES6, Promise is a new Object that can be instantiated

A resolve and reject functions are provided to either resolve or reject the promise.

var promise = new Promise(function(resolve, reject) {
  if (Math.random() >= 0.5) {
    resolve(true)
  } else {
    reject("It's < 0.5")
  }
})
Getting rid of callbacks with promises

This code:

var getUsers = function (callback, limit) {
  jQuery.get('//jsonplaceholder.typicode.com/users')
    .done(function (response) {
      callback(response.slice(0, limit))
    })
}

getUsers(function (users)
  console.log(users)
}, 5)

Becomes:

var getUsers = function(limit) {
  return new Promise(function(resolve, reject) {
    jQuery.get('//jsonplaceholder.typicode.com/users').done(function(response) {
      resolve(response.slice(0, limit))
    })
  })
}

getUsers(5).then(function(users) {
  console.log(users)
})

Practice

Print the number of photos of a user.

var getOneUser = function () { ... }
var getUserAlbum = function (userId) { ... }
var getAlbumPhotos = function (albumId) { ... }

var printUserFirstPhotos = function(){
  // YOUR CODE GOES HERE
}

printUserFirstPhotos() // 50

https://stackblitz.com/github/we-learn-js/js-training-code/tree/master/src/AsyncProgramming/xofebas-8?embed

If you ended up with a code like that:

var printUserFirstPhotos = function() {
  getOneUser().then(function(user) {
    return getUserAlbum(user.id).then(function(album) {
      return getAlbumPhotos(album.id).then(function(photos) {
        console.log(photos.length)
      })
    })
  })
}

You are still stuck with pyramides and not understanding promises...

What about that ?

var printUserFirstPhotos = function() {
  getOneUser()
    .then(user => getUserAlbum(user.id))
    .then(album => getAlbumPhotos(album.id))
    .then(photos => console.log(photos.length))
}

Or that ?

var printUserFirstPhotos = function() {
  getOneUser()
    .then(user => user.id)
    .then(getUserAlbum)
    .then(album => album.id)
    .then(getAlbumPhotos)
    .then(photos => photos.length)
    .then(console.log)
}

Promise static methods

  • Promise.all()
  • Promise.race()
  • Promise.reject()
  • Promise.resolve()

Promise.resolve(value)

A static method to create a promise resolved with the given value (or another promise)

This code

var promise = Promise.resolve(5)

is the same as

var promise = new Promise(function(resolve) {
  resolve(5)
})
var getSquare = function(x) {
  return Promise.resolve(x * x)
}

getSquare(4).then(square => console.log(square))

Promise.reject(reason)

A static method to create a promise reject with the given reason

This code

var promise = Promise.reject('Some error happened')

is the same as

var promise = new Promise(function(resolve, reject) {
  reject('Some error happened')
})

Promise.all(iterable)

A static method that returns a promise that will be resolved in an array of values of all given promises in the array.

var promise = Promise.all([
  Promise.resolve(4),
  Promise.resolve(5),
  Promise.resolve('a'),
  Promise.resolve({})
])

promise.then(values => console.log(values)) // [4,5,"a",{}]

Promise.all executes promises in parallel, not sequentially

var getUserPhotos = function(userId) {
  return getUser(userId)
    .then(user => getUserAlbum(user.id))
    .then(album => getAlbumPhotos(album.id))
    .then(photos => console.log(photos))
}

Promise.all([
  getUserPhotos(2),
  getUserPhotos(4),
  getUserPhotos(5),
  getUserPhotos(8)
])

Actually, it doesn't execute anything...

Promise.race(iterable)

A static method that returns a promise that resolves or rejects as soon as one of the promises in the iterable resolves or rejects, with the value or reason from that promise.

var getTimeoutPromise = function(time) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      reject('Timeout')
    }, time)
  })
}

var promise = Promise.race([getUserPhotos(), getTimeoutPromise(3000)])

If getUserPhotos() lasts more than 3 seconds, promise will be rejected with reason "Timeout"

Remember: promises are... always asynchronous

console.log('###### case 1 #####')
Promise.resolve(1)
  .then(x => console.log('then 1.0'))
  .then(x => console.log('then 1.1'))

console.log('###### case 2 #####')
Promise.resolve(1)
  .then(x => console.log('then 2.0'))
  .then(x => console.log('then 2.1'))

// "###### case 1 #####"
// "###### case 2 #####"
// "then 1.0"
// "then 2.0"

https://stackblitz.com/github/we-learn-js/js-training-code/tree/master/src/AsyncProgramming/kecemib?embed

Note: handlers are callbacks. Then, they get involved in the event loop.

Practice

Let's redo our practice about callbacks to play with promises.

var getUsers = function(limit) {
  /* Promise */
}
var getUserAlbums = function(userId, limit) {
  /* Promise */
}
var getAlbumPhotos = function(albumId, limit) {
  /* Promise */
}

function getUsersPhotos(limit) {
  // YOUR CODE GOES HERE
}

getUsersPhotos(6).then(photos =>
  console.log('Number of photos: ' + photos.length)
)
// OUTPUT : "Number of photos: 216"

https://stackblitz.com/github/we-learn-js/js-training-code/tree/master/src/AsyncProgramming/wemevaj-1?embed

Possible solution

function getUsersPhotos(limit) {
  return getUsers(limit)
    .then(users => users.map(user => getUserAlbums(user.id, limit)))
    .then(albumsPromises => Promise.all(albumsPromises))
    .then(usersAlbums => [].concat(...usersAlbums))
    .then(albums => albums.map(album => getAlbumPhotos(album.id, limit)))
    .then(photosPromises => Promise.all(photosPromises))
    .then(albumsPhotos => [].concat(...albumsPhotos))
}

Catching Rejections

Promise.prototype.catch()

The catch() method returns a Promise and deals with rejected cases only.

It behaves the same as calling Promise.prototype.then(undefined, onReject).

getUsersPhotos(6)
  .then(photos => console.log('Number of photos: ' + photos.length))
  .catch(e => console.log('getUsersPhotos call failed'))

MDN // Promise.prototype.catch()

catch() always returns a promise, like then

Let's see it in action:

https://stackblitz.com/github/we-learn-js/js-training-code/tree/master/src/AsyncProgramming/cibopuq?embed

Caution !

Be responsible. Catch your own errors and control your output.

function getUsersPhotos(limit) {
  return getUsers(limit)
    .then( users => users.map( user => getUserAlbums(user.id, limit) ) )
    .then( albumsPromises => Promise.all( albumsPromises ) )
    .then( usersAlbums => [].concat(...usersAlbums) )
    .then( albums => albums.map( album => getAlbumPhotos(album.id, limit) ) )
    .then( photosPromises => Promise.all( photosPromises ) )
    .then( albumsPhotos => [].concat(...albumsPhotos) )
    .catch( => return [])
}

A thing to remember

catch() is just sugar for `then(null, onRejection)``

This snippet...

getUsersPhotos(limit).catch(onReject)

... is exactly THE SAME as

getUsersPhotos(limit)
  .then (null, onReject })

On the other hand. This snippet...

getUsersPhotos(limit)
  .then(onResolve)
  .catch(onReject)

... is NOT the same as:

getUsersPhotos(limit).then(onResolve, onReject)

The snippet...

getUsersPhotos(limit)
  .then(onResolve)
  .catch(onReject)

It's exactly THE SAME as

getUsersPhotos(limit)
  .then(onResolve)
  .then(null, onReject)

Remember that the onReject catches errors from 'previous' promise that has not a rejection handler.

Each then, each catch returns a NEW Promise

What would be the output of this code?

var promise = Promise.resolve(1)

promise
  .then(x => {
    return x + 1
  })
  .then(x => {
    return x + 1
  })

var promise2 = promise.then(x => {
  return x + 1
})
var promise3 = promise2
  .then(x => {
    throw 'Error'
  })
  .catch(x => {
    return x
  })

promise.then(console.log)
promise2.then(console.log)
promise3.then(console.log)

https://stackblitz.com/github/we-learn-js/js-training-code/tree/master/src/AsyncProgramming/sigefo?embed

var promise = Promise.resolve(1) // Promise 1: value(1)

promise
  .then(x => {
    return x + 1
  }) // Promise 2: value(2)
  .then(x => {
    return x + 1
  }) // Promise 3: value(3)

var promise2 = promise.then(x => {
  return x + 1
}) // Promise 4: value(2)
var promise3 = promise2
  .then(x => {
    throw 'Error'
  }) // Promise 5: reason("Error")
  .catch(x => {
    return x
  }) // Promise 5: value("Error")

promise.then(console.log) // 1
promise2.then(console.log) // 2
promise3.then(console.log) // 3

Promise.prototype.finally

Handler executed whatever the promise is resolved or rejected.

showLoadingSpinner()
getUsersPhotos(6)
  .then( photos => console.log('Number of photos: ' + photos.length ) )
  .catch( e => console.log('getUsersPhotos call failed') )
  .finally( => hideLoadingSpinner() )

MDN // Promise.prototype.finally

Mastering Promises

Old promises patterns you must avoid

Promises have a long and storied history, and it took the JavaScript community a long time to get them right.

progress handler

Handler to notify of value resolution progress.

getJSON().then(
  function() {
    // resolution handler
    console.log('JSON loaded !')
  },
  function(e) {
    // rejection handler
    console.log('Error !')
  },
  function(progress) {
    // progress handler
    console.log(progress + '% loaded !')
  }
)

The deferred pattern (deferred objects)

var deferred = Q.defer()
FS.readFile('foo.txt', 'utf-8', function(error, text) {
  if (error) {
    deferred.reject(new Error(error))
  } else {
    deferred.resolve(text)
  }
})
return deferred.promise

More promise treats

Convert callback functions to promises

FS.readFile('foo.txt', 'utf-8', function(error, text) {
  /* ... */
})
var readFile = Q.denodeify(FS.readFile)
readFile('foo.txt', 'utf-8')
  .then(onResolve)
  .catch(onReject)
Q.nfcall(FS.readFile, 'foo.txt', 'utf-8')
  .then(onResolve)
  .catch(onReject)

Q.js

Convert promises to callbacks based libs

var getUsers = function(callback, limit) {
  window.fetch('//jsonplaceholder.typicode.com/users').asCallback(callback)
}

getUsers(function(err, result) {
  /* ... */
}, 5)

Bluebird

Delay with promises

Promise
  .delay(1000)
  .then( => getUsers() )

Bluebird

Further info on promises

Promise libraries

Promise based new APIs

Practice

Write getFirstCharNumber body code so it would return a promise resolved with the char number of the uppercase first letter of a given string.

function upper(text) { ... }

function firstChar(text) { ... }

function getChartCode(text) { ... }

getFirstCharNumber('abcde').then( console.log )

function getFirstCharNumber(text) {
  // YOUR CODE GOES HERE
}

https://stackblitz.com/github/we-learn-js/js-training-code/tree/master/src/AsyncProgramming/zidohun?embed

Possible solutions

function getFirstCharNumber(text) {
  return firstChar(text)
    .then(upper)
    .then(text => getChartCode(text)())
}
function getFirstCharNumber(text) {
  return Promise.resolve(upper(text)).then(text => getChartCode(text)())
}

async functions (ES7)

The async function declaration defines a async function.

When async function is called, it returns a promise.

When the async function returns a value, the promise will be resolved with the returned value.

MDN // async

Simple value promise:

function resolveAfter2Seconds(x) {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('resolved: ' + x)
      resolve(x)
    }, 2000)
  })
}
async function add1(x) {
  var a = await resolveAfter2Seconds(20)
  var b = await resolveAfter2Seconds(30)
  return x + a + b
}

add1(10).then(v => {
  console.log(v) // prints 60 after 4 seconds.
})

// "resolved: 20"
// "resolved: 30"
// 60

https://stackblitz.com/github/we-learn-js/js-training-code/tree/master/src/AsyncProgramming/wizojay?embed

Caution!

await pauses the execution, so watch out when you execute your promises

async function add2(x) {
  var a = resolveAfter2Seconds(20)
  var b = resolveAfter2Seconds(30)
  return x + (await a) + (await b)
}

add2(10).then(v => {
  console.log(v) // prints 60 after 2 seconds.
})

https://stackblitz.com/github/we-learn-js/js-training-code/tree/master/src/AsyncProgramming/coyovas?embed

Rejected promises

If an awaited promise is rejected, the reason value is thrown.

async function f3() {
  try {
    var z = await Promise.reject(30)
  } catch (e) {
    console.log(e) // 30
  }
}
f3()

Using async functions instead of promise chains

function getProcessedData(url) {
  return downloadData(url) // returns a promise
    .catch(e => {
      return downloadFallbackData(url) // returns a promise
    })
    .then(v => {
      return processDataInWorker(v) // returns a promise
    })
}
async function getProcessedData(url) {
  let v
  try {
    v = await downloadData(url)
  } catch (e) {
    v = await downloadFallbackData(url)
  }
  return processDataInWorker(v)
}

Must Read/Watch

Practices

Implement Promise.delay(ms)

Returns a promise that will resolved after given milliseconds.

Promise.delay = function(ms) {
  // YOUR CODE GOES HERE
}

Promise.delay(1000).then(function() {
  console.log('delayed 1000ms')
})

setTimeout(function() {
  console.log('delayed 500ms')
}, 500)

// OUTPUT "delayed 500ms" "delayed 1000ms"

https://stackblitz.com/github/we-learn-js/js-training-code/tree/master/src/AsyncProgramming/qidokig?embed

Solution

Promise.delay = function(ms) {
  return new Promise(function(resolve) {
    setTimeout(resolve, ms)
  })
}

Implement Promise.series(iterable)

Works like Promise.all, but executes the promises sequentially instead of in parallel.

var getDelayed = function(ms, name){ ... }

Promise.series = function(promises) {
  // YOUR CODE GOES HERE
}

Promise.series([
  getDelayed(500, 'promise 1'),
  getDelayed(400, 'promise 2'),
  getDelayed(300, 'promise 3')
])

// OUTPUT: "promise 1" "promise 2" "promise 3"

https://stackblitz.com/github/we-learn-js/js-training-code/tree/master/src/AsyncProgramming/mecalow?embed

Solution: NONE !!!!

You can't make promises change their execution order.

Once a promises is pending, it's in progress...

We could only implement it with:

Promise.series([
  function() {
    getDelayed(500, 'promise 1')
  },
  function() {
    getDelayed(400, 'promise 2')
  },
  function() {
    getDelayed(300, 'promise 3')
  }
])

Promise.series = function(promiseConstructors) {
  // YOUR CODE GOES HERE
}
Clone this wiki locally