Asynchronous javascript, from hell to exquisiteness

JS Sophia - 14th March, 2019

By Fred Guillaume

Agenda

  1. Sync vs Async
  2. Callbacks
  3. Promises
  4. Generators
  5. Await/async

Sync & async

  • How do we think about
  • How do we read
  • How do we write
  • code ?

Our brain is synchronous

  • High level thinking is synchronous, single tasked
  • Can't you multitask ? Because I can !!!
  • "Faking" multitasking
  • Lose focus
  • Quickly switching from one task to another

Our brain is synchronous

  • How do we work best ?
  • Do tasks one after the another
  • Our brain = event loop

Event loop

  • Single threaded
  • Run-to-completion
  • No parallel access
  • No shared memory

Event loop

http://latentflip.com/loupe/

Blocking the event loop

  • A blocking thread will prevent any other threads from running
  • Blocks the whole stack
  • Use of asynchronous callbacks

Blocking the event loop


          const end = Date.now() + 5000;
          while (Date.now() < end) {
            // Expensive operation blocking the event loop
          }
          

Blocking the event loop

  • Beware of expensive tasks
  • JSON.Parse & JSON.stringify
  • Regexp matching
  • Think about computation time & complexity

Expensive regexp


            app.get('/redos-me', (req, res) => {
              let filePath = req.query.filePath;
              
              // Nested quantifier, can be expensive !!!
              if (fileName.match(/(\/.+)+$/)) {
              console.log('valid path');
              }
              else {
              console.log('invalid path');
              }
              
              res.sendStatus(200);
            })
          

Callbacks

  • Call stack & callback queue
  • Avoid blocking the event loop
  • Execute code after an undefined (and unknown) amount of time
  • Callbacks execution are deferred later by third party code: inversion of control

Callback hell

  • What is callback hell ?

Code nesting


            api.getUser(loggin, function(err, data) {
              // ...
              api.getGeoLocation(user, function(err, data) {
                // ...
                api.getWeather(location, function(err, data) {
                  // ...
                  api.notifyWeather(weather, function(err, data) {
                    // ...
                  });
                });
              });
            });
          
  • Complex even with simple workflows

Tighlty coupled callbacks


            api.getUser(loggin, function(err, data) {
              // ...
              api.getGeoLocation(user, function(err, data) {
                // ...
                api.getWeather(location, function(err, data) {
                  // ...
                  api.notifyWeather(weather, function(err, data) {
                    // ...
                  });
                });
              });
            });
          

Execution issues

  • Callback executed too early...
  • ... or too late
  • Maybe never?
  • What about too many times ?
  • The only missing is too few times !!!!!

Error management


            try{
              api.getUser(loggin, function(err, data) {
                // ...
                api.getGeoLocation(user, function(err, data) {
                  // ...
                  api.getWeather(location, function(err, data) {
                    // ...
                    api.notifyWeather(weather, function(err, data) {
                      // ...
                    });
                  });
                });
              });
            }
            catch(err) {
              // Async errors not catched here !!!
            }
          

Some solutions exists

split callbacks


            const successCallback = (data) => console.log(data);
            const errorCallback = (error) => console.error(error);

            async(source, successCallback, errorCallback);
          

"Error first" style


              async(source, function(err, data) {
                // ...
              });
          

Still a lot to improve

  • success and error at the same time
  • Lot of cumbersome code needed to fix all these issues

Promises

  • Callback execution is handled by external code => IoC
  • What if we could uninvert that inversion of control ?

Promise "events"


            function foo(x) {
              // start doing something that could take a while

              // make a `listener` event notification
              // capability to return

              return listener;
            }

            var evt = foo( 42 );

            evt.on( "completion", function(){
              // now we can do the next step!
            } );

            evt.on( "failure", function(err){
              // oops, something went wrong in `foo(..)`
            } );
          

Flow control

  • Function can be called when promise resolves, regardless if it is a success or a failure

          function foo(promise) {
            promise.then(function() {
              // Code executed on promise fulfillment
            },function() {
              // Code executed on promise failure
            });
          }
        

Flow control

  • Function can be called only when promise is fulfilled, or is rejected

            promise.then(bar, errorBar);
          

Immutable once resolved

Immutable once resolved

Thenable duck typing

  • Any object or function that has a then method on it

Promisifying an async function


            // async(data, successCallback, errorCallback) function
            function asyncPromisified(data) {
              return new Promise(function(resolve, reject) {
                async(data, resolve, reject);
              });
            }
          

Promisifying an async function


            // async(data, callback) "error-first" style
            function asyncPromisified(data) {
              return new Promise(function(resolve, reject) {
                async(data, function(err,data) {
                  err ? reject(err) : resolve(data);
                });
              });
            }
          

Promise & job queue

  • When calling then, the callback will always be called asynchronously
  • Higher priority than event queue

Promise microtask queue

Promise trust

Call the callback too early

  • What happens if a promise is immediately fulfilled ?
  • In any cases, the then callback will be executed asynchronously through the microtask queue
  • Promise resolve synchronous & asynchronous code the same predictable way

Call the callback too late

  • When a promise is fulfilled, every then callback registered will be called in order in the next microtask queue execution

Callback never called

  • If a promise is resolved, its then callbacks will be called wether it is fulfilled or not
  • Promise.race

Callback called too few or too many times

  • A promise only resolves once, after it stays immutable
  • Then registered callbacks will only be executed once

Parameters issues

  • No parameters: resolves with undefined
  • More than one parameter: subsequent ones are ignored

Trustable ?

  • Promise.resolve
  • If pass a non-thenable value, it returns a new genuine promise fulfilled with that value
  • If pass a genuine promise value, it returns it back
  • If pass a non-genuine promise value, it unwraps that value up to its final non-thenable value

  • Promise.resolve always return a trustable genuine promise value

Chain flow

  • The value returned from a fulfillment callback is automatically set as the fulfillment of the next chained promise
Callbacks

Promise advanced

  • Default handlers

            // Success handler
            function(v) {
              return v;
            }

            // Error handler
            function(err) {
            throw err;
            }
          

Promise advanced

  • Fulfill handler unwrap promise value
  • Error handler does not unwrap promise value

Promise advanced

  • Swallowed errors
  • If an error is thrown in then callbacks, it will affect the next chained promise
  • Add a catch statement at the end
  • Errors in that catch, what about the promise returned by the catch method ?

Promise patterns

  • Promise.all([])
  • Fulfilled if every promise are fulfilled. If one is rejected, Promise.all is immediately rejected
  • An array of every fulfillment messages is passed to the then fulfill callback

Promise patterns

  • Promise.race([])
  • Argument: array of promise instances
  • Fulfilled when any promise fulfill, and rejected when any promise reject

Promise limitations

  • Error management
    • Can handle error at the end of the chain
    • But errors might be silenced inside the chain
    • Same as try/catch statement that silent an error
  • No reference to intermediate steps
  • Single value parameter (must use object or array)
  • Only single resolution
  • Cannot be canceled
  • Less performant (but more error-proof) => achieve the same with callbacks ?
  • Code boilerplate

Generators

  • Express async flow control in a sequential style
  • Iterable: object with a Symbol.iterator property
  • Iterator: object with
    • next() method
    • value property
    • done property

Generators

  • When called, a generator function will return an iterator
  • When a value is consumed with the iterator (next method), the generator function executes until it encounters the yield keyword
  • return method on iterator to send the terminal signal
  • A finally clause in the generator function will always be called upon termination

Generators

  • Multiple iterators
  • Interleaving

Generators

  • What if we yield a promise ?
  • Pause the generator by getting back a promise, and resume it when that promise fulfills or rejects !!!

Async/await

  • Async control flow ... in a sequential way !!! Awesome !!!

TL;DR

Previous limitations:

  • Code boilerplate
  • Error management
  • No reference to intermediate steps

Still:

  • Single value parameter
  • Only single resolution
  • Cannot be canceled
  • Less performant (but more error-proof) => achieve the same with callbacks ?

To be continued:

  • Observables
  • Multiple events (data streams)
  • Cancelable
  • Useful operators
  • RxJS

Playground