DEV Community

Cover image for ES6 Promises
Connor Dillon
Connor Dillon

Posted on

ES6 Promises

A Promise is an object that represents the eventual completion (or failure) of an asynchronous operation, resulting in a value.

The Promise constructor is primarily used to wrap functions that do not already support promises.

Essentially, a promise is a returned object to which you attach callbacks instead of passing callbacks into a function.

A callback can be added to a promise with the use of the then() method. Multiple callbacks can be added by calling then() several times. Each callback is executed one after another, in the order they were inserted.

Syntax

new Promise(executor)

Parameters

executor
A function that is passed with the arguments resolve and reject.
The executor is called to initiate asynchronous work before the Promise constructor even returns the created object.
Once the executor finishes it's work, calls the resolve function to resolve the promise or else rejects it if an error occurred.
If an error is thrown in the executor function, the promise is rejected.
The return value of the executor is ignored.

Description

A Promise is a proxy for a value not necessarily known when the promise is created.
It allows you to associated handlers with async action's eventual success value or failure reason.
This lets async methods return values like synchronous methods: instead of immediately returning the final value, the async method returns a promise to supply the value at some point in the future.

A Promise is in one of these states:

pending: initial state, neither fulfilled nor rejected.
fulfilled: operation completed successfully.
rejected: operation failed.

A Promise can be either fulfilled with a value or rejected with a reason (error).
When either case happens, the associated handlers queued up by a promises's then method are called.

As the Promise.prototype.then() and Promise.prototype.catch() methods return promises, and can be chained.

Basic Structure

const myPromise = new Promise((resolve, reject) => {
  // do something asynchronous which eventually calls either:
  //
  //   resolve(someValue); // fulfilled
  // or
  //   reject("failure reason"); // rejected
})
Enter fullscreen mode Exit fullscreen mode

Old "Callback Style" vs. Promises

Imagine a function, createAudioFileAsync(), which asynchronously generates a sound file given a configuration record and two callback functions, one called if the audio file is successfully created, and the other called if an error occurs.

Here's some code that uses createAudioFileAsync():

function successCallback(result) {
  console.log('Audio file ready at URL: ' + result)
}

function failureCallback(error) {
  console.error('Error generating audio file: ' + error)
}

createAudioFileAsync(audioSettings, successCallback, failureCallback)
Enter fullscreen mode Exit fullscreen mode

…modern functions return a promise you can attach your callbacks to instead:

If createAudioFileAsync() were rewritten to return a promise, using it could be as simple as this:

createAudioFileAsync(audioSettings).then(successCallback, failureCallback)
Enter fullscreen mode Exit fullscreen mode

That's shorthand for:

const promise = createAudioFileAsync(audioSettings)
promise.then(successCallback, failureCallback)
Enter fullscreen mode Exit fullscreen mode

Chaining

A common need is to execute two or more asynchronous operations back to back, where each subsequent operation starts when the previous operation succeeds, with the result from the previous step. We accomplish this by creating a promise chain.

Here's the magic: the then() function returns a new promise, different from the original:

const promise = doSomething()
const promise2 = promise.then(successCallback, failureCallback)
Enter fullscreen mode Exit fullscreen mode

or

const promise2 = doSomething().then(successCallback, failureCallback)
Enter fullscreen mode Exit fullscreen mode

This second promise (promise2) represents the completion not just of doSomething(), but also of the successCallback or failureCallback you passed in, which can be other asynchronous functions returning a promise. When that's the case, any callbacks added to promise2 get queued behind the promise returned by either successCallback or failureCallback.

Basically, each promise represents the completion of another asynchronous step in the chain.

Promise Prototype Methods

Promise.prototype.catch()

Appends a rejection handler callback to the promise, and returns a new promise resolving to the return value of the callback if it is called, OR to its original fulfillment value if the promise is instead fulfilled.

Promise.prototype.then()

Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler, or to its original settled value if the promise was not handled (i.e. if the relevant handler onFulfilled) or onRejected is not a function).

Promise.prototype.finally()

Appends a handler to the promise, and returns a new promise which is resolved when the original promise is resolved. The handler is called when the promise is settled, whether fulfilled or rejected.

Examples

To provide a function with promise functionality, simply have it return a promise:

function myAsyncFunction(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    xhr.onload = () => resolve(xhr.responseText)
    xhr.onerror = () => reject(xhr.statusText)
    xhr.send()
  })
}
Enter fullscreen mode Exit fullscreen mode

Using Promise and XMLHttpRequest to load an image:

function imgLoad(url) {
  // Create new promise with the Promise() constructor;
  // This has as its argument a function
  // with two parameters, resolve and reject
  return new Promise(function (resolve, reject) {
    // Standard XHR to load an image
    var request = new XMLHttpRequest()
    request.open('GET', url)
    request.responseType = 'blob'
    // When the request loads, check whether it was successful
    request.onload = function () {
      if (request.status === 200) {
        // If successful, resolve the promise by passing back the request response
        resolve(request.response)
      } else {
        // If it fails, reject the promise with a error message
        reject(Error("Image didn't load successfully; error code:" + request.statusText))
      }
    }
    request.onerror = function () {
      // Also deal with the case when the entire request fails to begin with
      // This is probably a network error, so reject the promise with an appropriate message
      reject(Error('There was a network error.'))
    }
    // Send the request
    request.send()
  })
}
// Get a reference to the body element, and create a new image object
var body = document.querySelector('body')
var myImage = new Image()
// Call the function with the URL we want to load, but then chain the
// promise then() method on to the end of it. This contains two callbacks
imgLoad('myLittleVader.jpg').then(
  function (response) {
    // The first runs when the promise resolves, with the request.response
    // specified within the resolve() method.
    var imageURL = window.URL.createObjectURL(response)
    myImage.src = imageURL
    body.appendChild(myImage)
    // The second runs when the promise
    // is rejected, and logs the Error specified with the reject() method.
  },
  function (Error) {
    console.log(Error)
  }
)
Enter fullscreen mode Exit fullscreen mode

References and Links

Top comments (0)