Find file History
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
..
Failed to load latest commit information.
images
README.md
node-api-promises_notes.pdf

README.md

LESSON: node-api-promises


2018-12-09

OBJECTIVES

By the end of this, developers should be able to:

  • Explain the value of using promises instead of callback interfaces.
  • Read Node documentation that uses callbacks and translate that into implementations using promises.
  • Rewrite Node scripts using callbacks as scripts using promises.

VOCABULARY

Synchronous Function: Any function that will wait to complete before continuing running any code. Synchronous functions are called blocking in that they block the process in which they are invoked from continuing until they are resolved.

Asynchronous Function: Any function which will not resolve immediately after it is invoked. Asynchronous functions are called non-blocking in that they do not block the process in which they are invoked from continuing. The way node handle asynchronous functions is allowing them to split off and run on their own process, while the original process that original invoked the asynchronous function can keep running. This can make handling asynchronous functions and dealing with their outputs more complex than with synchronous functions.

Callback Function: A function passed into another function as one of it's arguments. A callback function can be run from within the function it is passed into, which allows for us to wait for an asynchronous process to complete within the function, and then once complete, run the callback function. The callback function is often passing the resulting data from the asynchronous process.

Blocking: An expression that blocks the process of running code, and will wait for that expression to resolve before continuing.

Non-blocking: An expression that does not block the process of running code, and will start running that expression on a separate process, while continuing to run the code after that expression.

Promise: An object that allows us to do something asynchronous and then easily attach callbacks to run in the case of success or failure for that asynchronous process. The Promise constructor allows us to create these objects. This asynchronous action is specified as the only argument to the Promise constructor, a callback function called the executor. The .then and .catch methods available on each Promise instance allow us to easily attach callback functions to handle success and failure of the Promise, respectively.

Executor Function: The only argument to a Promise constructor, which executes some asynchronous task, then reports back whether the task completed successfully or not. The executor always takes in two parameters, resolve and reject. When you make a new Promise object, it runs the executor function and passes it two functions, resolve an reject, which the executor can use within it's function block. The executor can indicate a successful resolution of the task (by calling resolve) or a failure of the task (by calling reject). Calling resolve will take whatever data is passed into the resolve function and then run the callback specified in then method of the Promise with that data as the argument. Calling reject will take whatever data is passed into the reject function and then run the callback specified in catch method of the Promise with that data as the argument.

NOTES

  • Many tasks we want to run in node execute asynchronously, such as server requests, reading files, writing files, etc. We cannot simply put consecutive commands in order in our code when one of them is asynchronous, because node will start the asynchronous process and move on and run the next lines before the asynchronous process has completed. Often, we need tasks to run in a particular order, regardless of whether they are asynchronous or synchronous. How can we ensure that tasks run in the desired order?

  • Promises allow for asynchronous actions to be handled more cleanly than callbacks alone, allowing for much easier comprehension of execution order and unified error handling.

    • There is only one error handling function specified using .catch
    • You can chain .then callbacks one after another and they will run strictly in that order, waiting for each previous callback to finish before running the next one
      • the result of a previous .then callback will automatically be piped into the input of the next .then callback
      • if an error occurs along the way, the .catch callback will be run and receive the error object as it's argument
      • control flow in promises
  • In many cases, you will be dealing with functions that are already built to return a Promise to you, so you can simply add .then and .catch methods to it as follows:

  • asyncPromiseFunction()
      .then(successCallback)
      .catch(failureCallback)
    • this is exactly what we did with $.ajax() in our projects
  • You can chain multiple .then callbacks to run:

  • asyncPromiseFunction()
      .then(firstThing)
      .then(secondThing)
      .then(thirdThing)
      .catch(failureCallback)
  • When you want to write your own Promise returning function, you should always stick to this skeleton:

    const promiseSkeletonFunction = (<args>) => {
        return new Promise(resolve, reject) => {
            //some async thing that returns a result
            if (<result is failure>) {
                reject(<failure data>)
            } else {
                resolve(<success data>)
            }
        }
    }

EXAMPLES

Callbacks Are Useful to Handle Asynchronicity

  • node runs things asynchronously by default. We'll see that it does not wait for asynchronous things to finished before running the next line.

  • /* We want the code below to wait 4 seconds then log 'almost...', then '...done'
    */
    
    // an asynchronous function
    const asynchFunctionWithoutCallback = () => {
      // this will console.log the text after 4 seconds
      setTimeout(() => {
        console.log('almost...')
      }, 4000)
    }
    
    // another function
    const nextFunction = () => {
      console.log('I\'m done')
    }
    
    // we want these to print in the order they are run
    asynchFunctionWithoutCallback()
    nextFunction()
    // => "I'm done
    // waits 4 seconds...
    // => "almost..."
    
    // :-(
  • In order to get the functions to execute in the right order, we can use callbacks:

  • // an asynchronous function w/ a callback
    const asynchFunctionWithCallback = (callback) => {
      // this will console.log the text after 4 seconds
      setTimeout(() => {
        console.log('almost...')
        callback()
      }, 4000)
    }
    
    asynchFunctionWithCallback(nextFunction)
    
    // waits 4 seconds...
    // => "almost..."
    // => "I'm done
    
    // :-)
  • That being said, heavily nesting callbacks to handle sequences of tasks containing multiple asynchronous tasks can lead to callback hell. Using the Promise API provides a better API to avoid callback hell.

Promise Example: Fake Server

  • In this example, we have a fake server that will return a successful or failed response at random after a 2 second wait:

  • const serverRequestSimulation = () => {
      return new Promise((resolve, reject) => {
        // wait 2 seconds then run finished
        setTimeout(() => {
          finished()
        }, 2000)
    
        // after two seconds, randomly pick whether
        // we succeeded or failed
        const finished = () => {
          const succeeded = Math.random() >= 0.5
    
          // return back a success message
          if (succeeded) {
            resolve('you were successful')
          // return back an error message
          } else {
            reject('you have utterly failed')
          }
        }
      })
    }
    
    // success handler
    const serverError = (err) => {
      console.log('ERROR! HERE IS YOUR ERROR: ' + err)
    }
    
    // error handler
    const serverSuccess = (data) => {
      console.log('SUCCESS! HERE IS YOUR DATA: ' + data)
    }
    
    // running this function returns back an Promise object that
    // is already running it's executor function
    serverRequestSimulation()
      // if the request was successful, run
      // serverSuccess function
      .then(serverSuccess)
      // if the request was successful, run
      // serverFailure function
      .catch(serverError)
  • If we want to be able to reuse promises, we should always wrap them in a function as above that returns a new Promise. This allows us to use .then and .catch to assign success and failure callbacks to the promise as seen above.

  • We can see that our executor function takes in resolve and reject as arguments, and after completing the asynchronous task (waiting 2 seconds), randomly picks success or failure and uses resolve or reject to run the serverSuccess or serverError callbacks in either case.