JavaScript - Promises - What is a promise?

javascript-promises

Definition and basic information about promises:

While synchronous code is easier to follow and debug, async is generally better for performance and flexibility. Why "hold up the show" when you can trigger numerous requests at once and then handle them when each is ready? Promises are becoming a big part of the JavaScript world, with many new APIs being implemented with the promise philosophy.

A Promise object represents a value that may not be available yet, but will be available at some point in the future. It allows us to write asynchronous code in a more synchronous fashion. For example, if we use the promise API to make an asynchronous call to a remote web service we will create a Promise object which represents the data that will be returned by the web service in future. The caveat being that the actual data is not available yet. It will become available when the request completes and a response comes back from the web service. In the meantime, the Promise object acts like a proxy for the actual data. We can attach callbacks to the Promise object which will be called once the actual data is available.

Promises give us a way to handle asynchronous processing in a more synchronous fashion. They represent a value that we can handle at some point in the future. Promises give us guarantees about that future value, specifically:

  1. No other registered handlers of that value can change it (the Promise is immutable).
  2. We are guaranteed to receive the value, regardless of when we register a handler for it, even if it is already resolved, unless it is rejected, and in which case, if we registered a failure callback, our failure callback is invoked.
function fetchRandomQuote() {
  var promise = new Promise(function(resolve, reject) {
    var request = new XMLHttpRequest();
    request.open('GET', 'http://api.icndb.com/jokes/random');
    request.onload = function() {
      if (request.status == 200) {
        resolve(request.response); // we got data here, so resolve the Promise
      } else {
        reject(Error(request.statusText)); // status is not 200 OK, so reject
      }
    };

    request.onerror = function() {
      reject(Error('Error fetching data.')); // error occurred, reject the  Promise
    };

    request.send(); //send the request
  });
  return promise;
}

var promise = fetchRandomQuote();
promise
  .then(function(data) {
    console.log('Quote: ' + data);
  })
  .catch(function(error) {
    console.log(error.getMessage());
  });

In the above code, we start by instantiating a new Promise object and passing it a callback function. The callback takes two arguments, resolve and reject, which are both functions. All your asynchronous code goes inside that callback. If everything is successful, the promise is fulfilled by calling resolve(). In case of an error, reject() is called with an Error object.

In the above code, when a JSON response is received from the remote server, it is passed to the resolve() method. In case of any error, reject() is called with an Error object.

var p = new Promise((resolve, reject) => {
  setTimeout(() => {
    if (true) {
      resolve('success data');
    } else {
      reject('An error has occurred.');
    }
  }, 2000);
});

p.then((data) => {
  console.log(data);
});

p.catch((error) => {
  console.error(error);
});

The process of setting a value to the promise is called "resolving the promise". If we can not assign a value to a promise (failure condition), we reject the promise with a reason. Resolving or rejecting a promise is called fulfilling the promise.

The callback that is specified as the first parameter for the .then method is invoked when the promise is resolved. The callback function specified as the first parameter of the .catch method is invoked when the promise is rejected.

The .then method can take two parameters, which are both functions / callbacks. The first parameter is the success callback, and the second parameter is the failure callback. However, this can make our code harder to read. Therefore, we should only specify one parameter, the success callback, and we should use the .catch method to specify the failure callback. We can specify multiple success callbacks, and multiple failure callbacks.

When the promise is resolved, the success callbacks are invoked. When the promise rejected, the failure callbacks are invoked.

If we specify multiple success callbacks, only the first callback receive the data that was passed to the resolve method. If the first success callback return a value or object, that value or object will be passed to the second success callback as a parameter.

We can use the .then method multiple times, and the callbacks are called in the order specified. Each callback receive the result of the previous callback. In other words, callbackA is specified before callbackB, callbackB will not be invoked until callbackA has finished.

If we do not wish to use the .catch method, we can specify a "failure" callback as the second parameter of the then method.

p.then((data) => console.log('success: ', data), (error) => console.error(error));

The above code is a bit hard to read, so we should write it as:

p.then(
  (data) => {
    console.log('success: ', data);
  },
  (error) => {
    console.error(error);
  }
);

As a best practice, we should always use the the .catch method instead of specifying the "failure" callback as the second parameter to the .then method.

When we resolve a promise, we can specify the data, which is passed to the callback of the .then method. In other words, if the promise should resolve to 5, we would resolve the promise with:

resolve(5)

and 5 is the value that is passed as parameter to the first then callback.

var promise = asyncFunction();

promise.then(function(value) {
  // do something with the value
});

return promise;

Saving the promise inside a variable is not always required, and we can often omit it:

return asyncFunction().then(function(value) {
  // do something with the value here
});

How can we create and use promises?

var p = new Promise((resolve, reject) => {
  setTimeout(() => {
    if (true) {
      resolve('success data');
    } else {
      reject('An error has occurred.');
    }
  }, 2000);
});

p.then((data) => {
  console.log(data);
});

p.catch((error) => {
  console.error(error);
});

Review the entire content of this page if there is any questions / confusion.

What do we mean when we say that we resolve or reject a promise?

We mean that we provide the final value for the promise, or we indicate that there was an error. When we construct a promise, we pass a function to the Promise constructor. The framework invokes our function with two special parameters. These are functions. We invoke the first function to resolve the promise, or we invoke the second function to indicate that an error occurred.

function fetchRandomQuote() {
  var promise = new Promise(function(resolve, reject) {
    var request = new XMLHttpRequest();
    request.open('GET', 'http://api.icndb.com/jokes/random');
    request.onload = function() {
      if (request.status == 200) {
        resolve(request.response); // we got data here, so resolve the Promise
      } else {
        reject(Error(request.statusText)); // status is not 200 OK, so reject
      }
    };

    request.onerror = function() {
      reject(Error('Error fetching data.')); // error occurred, reject the  Promise
    };

    request.send(); //send the request
  });
  return promise;
}

var promise = fetchRandomQuote();
promise
  .then(function(data) {
    console.log('Quote: ' + data);
  })
  .catch(function(error) {
    console.log(error.getMessage());
  });

The above code show us how to resolve or reject a promise. I do not know if there is a way to externally resolve or reject a promise, outside of the function that we pass to the Promise constructor.

What are promise statuses / states?

A promise can have 3 states:

  1. pending (not yet resolved or rejected)
  2. fulfilled
  3. rejected

The promise.status property, which is code-inaccessible and private, contains information about these states. When a promise is rejected or resolved, this status is permanently associated with it. This means that a promise can be success or failed only once.

What happens when we resolve or reject a promise?

If the promise is resolved, the control will go to the then callbacks. If the promise is rejected, the control will move forward to the next then() that has a failure callback, or to the next catch(). Apart from explict promise rejection, the catch callback is also called when any exception is thrown. Throwing an exception in a Promise automatically reject that promise.

In what order are the then callbacks executed?

The then callbacks and the catch callbacks are executed in the the order that they were registered. The second then callback is not invoked until the first callback finishes.

What happens if there is an error or if an exception was thrown?

Apart from explict promise rejection, the catch callback is also called when any exception is thrown. Throwing an exception in a Promise automatically reject that promise.

If an error is thrown, it automatically invoke the callback methods that are specified using the the .catch method. Sub-sequent "then" callbacks (without the failure callback) are not invoked if an error occurred within the first "then" callback.

What happens if we attach a then callback, or a catch callback to a promise that is already resolved or rejected?

If a promise has already been fulfilled (either resolved or rejected), and we later attach callbacks to it, the callbacks will be immediately invoked.

If a promise has already been resolved but then is called again, the then callback is immediately invoked. If the promise was rejected, and we call the then function, the then callback is never invoked, but the failure function is invoked if we specified the failure function.

How can we pass values between callbacks?

Each then callback receive the result of the previous then callback return value. If the previous then callback returns a promise, the sub-sequent then callback will wait until this returned promise is either resolved or rejected. If the first then callback returns a promise, the second then callback is not invoked until that promise is resolved.

If we register multiple then callbacks, only the first then callback receives the resolved value as parameter. Sub-sequent .then callbacks do not receive this data. Instead, they receive the result of the previous .then callbacks.

When we return a simple value inside then(), the next then() is called with that return value. But if we return a promise inside then(), the next then() waits on it and gets called when that promise is settled. Is this really the case? Does the second then callback really receive the data from the second promise or does it receive the second promise as a parameter?

Can the then callbacks return values?

Yes. We may often incorrectly assume that these then callbacks always perform additional asynchronous operations and therefore cannot return value. This is not necessarily true. These then callbacks are functions, and they can always return value. If we need to do asynchronous operation inside these then callbacks, we should return a promise that does the asynchronous operation.

Each then callback receive the result of the previous then callback return value. If the previous then callback returns a promise, the sub-sequent then callback will wait until this returned promise is either resolved or rejected. If the first then callback returns a promise, the second then callback is not invoked until that promise is resolved.

If we register multiple then callbacks, only the first then callback receives the resolved value as parameter. Sub-sequent .then callbacks do not receive this data. Instead, they receive the result of the previous .then callbacks.

When we return a simple value inside then(), the next then() is called with that return value. But if we return a promise inside then(), the next then() waits on it and gets called when that promise is settled. Is this really the case? Does the second then callback really receive the data from the second promise or does it receive the second promise as a parameter?

What does the then function and the catch function always return?

The .then function and the .catch function always returns a promise, which may be the same promise that it was invoked with (the this object). This allows us to chain multiple then callbacks:

return asyncFunction()
  .then(function(value) {...})  // first callback
  .then(function(value) {...}); // second callback

Here, when we say the 'then function or the catch function', we are not referring to the callback functions. Instead, we refer to the .then method or the .catch method that are members of the promise object.

Can we have a function that create a promise with some then or catch callbacks attached to the promise?

Yes. We can have a function that creates a promise, attach some callbacks to the promise, and returns the promise. The caller of our function can attach more callbacks to the promise if desired. If any of the then callback throws an exception, the control will go to the next .catch callback as normal.

How can we externally resolve or reject a promise?

We cannot have an empty promise. We cannot externally resolve a promise, using Promise.resolve(aPromise). Perhaps we can create a promise using new Promise() without specifying executor function, or we can create a promise using Promise.resolve(), but in the case of using Promise.resolve(), the promise is already resolved. I do not know how to externally resolve a promise yet. Perhaps, in such cases, we have to use Deferred?

Alternatively, perhaps, to resolve a promise externally, we can use the bind function:

var p = new Promise(function(resolve, reject){
    this.onclick = resolve;
}.bind(this));

See:

  1. http://bit.ly/2r7PNqH
  2. http://lea.verou.me/2016/12/resolve-promises-externally-with-this-one-weird-trick/
  3. http://bit.ly/2r7PNqH

Can we use Promise.resolve and Promise.reject to resolve or reject an existing promise? How can we use Promise.resolve and Promise.reject?

No. Promise.resolve and Promise.reject always returns a new promise that is already resolved or rejected.

Promise.resolve() and Promise.reject() are handy in situations where we need to create a promises that always resolve, or promises that always reject.

Promise.reject(reason): Returns a Promise object that is rejected with the given reason.

Promise.resolve(value): Returns a Promise object that is resolved with the given value. If the value is a thenable (i.e. has a then method), the returned promise will "follow" that thenable, adopting its eventual state; otherwise the returned promise will be fulfilled with the value. Generally, if you don't know if a value is a promise or not, Promise.resolve(value) it instead and work with the return value as a promise.

Sometimes, we do not need to complete an async task within the promise. In that case, we can simply invoke Promise.resolve() or Promise.reject() without using the new keyword:

var userCache = {};

function getUserDetail(username) {
  // In both cases, cached or not, a promise will be returned

  if (userCache[username]) {
    // Return a promise without the "new" keyword
    return Promise.resolve(userCache[username]);
  }

  // Use the fetch API to get the information
  // fetch returns a promise
  return fetch('users/' + username + '.json')
    .then(function(result) {
      userCache[username] = result;
      return result;
    })
    .catch(function() {
      throw new Error('Could not find user: ' + username);
    });
}

The above code use Promise.resolve() and the fetch function, and in both of these cases, it always return a promise, not the user data. The user's data is passed to the subsequent then callback.

In the above code, only minimal stuff is done inside the function, and because it always return a promise, the caller of our function can do additional processing, or error handling by adding additional then callbacks or catch callbacks.

When is the function passed to the Promise constructor invoked?

The Promise constructor probably creates a promise object, do some necessary setup, use setTimeout or setInterval to invoke the provided function with a small timeout / interval, and return the promise object. We, as the developer, probably do not invoke this function directly, unless we are implementing our own promise library.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License