ES6 Promises
Tags: javascript
Published: 2020-07-31 02:32:24

<!-- TOC -->

- [ES6 Promises](#es6-promises)
    - [Syntax](#syntax)
        - [Parameters](#parameters)
    - [Description](#description)
    - [Basic Structure](#basic-structure)
        - [Old "Callback Style" vs. Promises](#old-callback-style-vs-promises)
        - [Chaining](#chaining)
    - [Promise Prototype Methods](#promise-prototype-methods)
    - [Examples](#examples)
    - [References and Links](#references-and-links)

<!-- /TOC -->

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

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

### 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()`:

```javascript
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)
```

…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:

```javascript
createAudioFileAsync(audioSettings).then(successCallback, failureCallback)
```

That's shorthand for:

```javascript
const promise = createAudioFileAsync(audioSettings)
promise.then(successCallback, failureCallback)
```

### 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:

```javascript
const promise = doSomething()
const promise2 = promise.then(successCallback, failureCallback)
```

or

```javascript
const promise2 = doSomething().then(successCallback, failureCallback)
```

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:

```javascript
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()
  })
}
```

Using `Promise` and `XMLHttpRequest` to load an image:

```javascript
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)
  }
)
```

## References and Links

- **[MDN: Promise Constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)**
- **[MDN: Using Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises)**