Javascript uses callback functions to handle asynchronous programming. There is an adaptation process from synchronous to asynchronous callback programming, but having multiple layers of nested callbacks, known as the Pyramid of Doom, is definitely a bad programming experience. Thus came CommonJS ‘Promises/A specification for the callback pyramid. This article first introduces the norms of Promises, and then interprets a mini Promises to deepen understanding.

What is a Promise

A Promise object represents a value that is not currently available, but can be resolved at some point in the future. It allows you to write asynchronous code in a synchronous manner. For example, if you want to invoke a remote server asynchronously using the Promise API, you need to create a Promise object that represents data that will be returned by the Web service in the future. The only problem is that the data is not yet available. The data becomes available when the request completes and is returned from the server. During this time, the Promise object acts as a proxy for real data. Next, you can bind a callback function to the Promise object that will be called once the real data becomes available.

Promise objects have existed in many languages in many forms.

Remove the Pyramid of Doom

The most common anti-pattern in Javascript is to nest callbacks inside callbacks.

/ / callback pyramid asyncOperation (function (data) {/ / processing ` data ` anotherAsync (function (data2) {/ / processing ` data2 ` YetAnotherAsync (function(){// finish}); }); });Copy the code

Introduce the code after Promises

PromiseSomething ().then(function(data){// Data 'return anotherAsync(); }). Then (function(data2){return 'data2' yetAnotherAsync(); }). Then (function(){// finish});Copy the code

Promises to transform nested callback into a series of concatenated.then calls, eliminating the bad code style of indentation. Promises are not an algorithm to solve a specific problem, just a better way to organize code. Embrace new organizational patterns and come to understand asynchronous invocation in a new light.

Each language platform has a corresponding Promise implementation

  • Java’s java.util.concurrent.Future
  • Python’s Twisted deferreds and PEP-3148 futures
  • F#’s Async
  • .Net’s Task
  • C++ 11’s std::future
  • Dart’s Future
  • Javascript’s Promises/A/B/D/A+

Let me take a look at some of the details of the various specifications in the javascript locale.

Promises/A specification

A promise represents a final value that is returned when an operation completes.

  • Promise has three states: ** fails (unfulfilled), ** completes (fulfilled) and ** fails (fulfilled).
  • The state of a promise can only be changed from ** unfinished ** tocompleteOr ** incomplete ** converts to ** failed **.
  • Promise state transitions happen only once.

Promise has a THEN method that takes three functions as arguments. The first two functions correspond to the two callback functions of state fulfilled and Rejected of promise. The third function processes progress information (support for progress callbacks is optional).

PromiseSomething () {// This is a big pity. Call this function},function(rejected){// Call this function when the promise state is rejected,function(progress){// call this function when the promise state is rejected);Copy the code

If a promise supports one of the following additional methods, it is called an interactive promise

  • Get (propertyName) gets a property on the final value of the current Promise, which returns a new Promise.
  • call(functionName, arg1, arg2, …) Calling a method on the final value of the promise, of course, returns a new promise.

Promises/B spec

Promises/B Building on Promises/A, Promises/B defines A set of apis that promise modules need to implement

When (value, callback, errback_opt) If value is not a promise, then the next event loop callback will be called with value passed in as the callback value. If value is a Promise, the state of the promise is completed or becomes completed, then the next event loop callback is called and the value of resolve is passed to the callback. When the promise state has failed or becomes a failure, the next event loop errback is called and Reason is passed in as the reason for the failure.

The biggest difference between ASAP (value, callback, errback_opt) and when is that if value is not a promise, it will be executed immediately without waiting for the next event loop.

Enqueue (Task Function) invokes the Task method in the next event loop as soon as possible.

Get (object, name) returns a promise to get the object’s properties.

Post (Object, Name, args) returns a promise to call the object’s method.

Put (Object, Name, Value) returns a promise to modify the object’s properties.

Del (object, name) returns a promise to delete an object property.

MakePromise (Descriptor Object, fallback Function) returns a Promise Object, which must be a callable Function or possibly a constructor that can be instantiated.

  • The first argument takes a description object of the following structure, {“when”: function(errback){… }, “get”: function(name){… }, “put”: function(name, value){… }, “post”: function(name, args){… }, “del”: function(name){… },} Each of the above registered Handles returns an Resolved value or promise.

  • The second argument accepts a fallback(message,… Args), which will be triggered when there is no promise object and the corresponding handle is not found, and return an Resolved value or promise.

Defer () returns an object that contains a resolve(value) method and a Promise property. When the resolve(value) method is called for the first time, the state of the promise property changes to complete, and the states of all promises observed before or after the promise are changed to complete. The value argument, if not a Promise, is wrapped as a promise ref. The resolve method ignores all subsequent calls.

Reject (Reason String) Returns a promise marked as a failure. When the when(message) method is called on a failed promise, one of two methods is used: 1. If errback is present, errback is called with Reason as an argument. The when method returns the value of errback. 2. If errback is not present, the when method returns a new Reject promise object with the same Reason as an argument.

Ref (value) If value is a Promise object, return value itself. Otherwise, return a Resolved Promise with the following Handle. When (errback) : ignore errback and return resolved value. 2. Get (name) : Return resolved value. 3. Put (name, value), set the resolved value. 4. Del (name), delete the resolved value. 5. Post (name, args) to call the resolved method. 6. All other calls return a reject, with a “Promise does not handle NAME” justification.

IsPromise (value) Boolean Determines whether an object is a Promise

Method (Name String) gets a Promise that returns a method for name. The return values are “get”, “put”, “del”, and “post” corresponding methods, but will be returned in the next event loop.

Promises/D specification

In order to increase interoperability between different promise implementations, Promises/D specification makes further Promises between promise objects and Promises/B specification. To achieve the duck-type Promise.

Promises/D specification, in a nutshell, does two things,

  1. How to tell if an object is of type Promise.
  2. Promises/B specification details are added.

Identify a Promise object

The Promise object must be the implementation of the promiseSend method. In the promise library context, an object can be identified as a promise object if it contains a promiseSend method. 2. The promiseSend method must accept an action name as its first argument. The operation name is an expandable collection and here are some reserved names 1.when the third argument must be rejection callback. Rejection callback must accept a rejection reason (any value) as the first argument in the rejection callback (get) and put (put) as the third argument and as the fourth argument as the value of the new attribute. 5. Post is the property name of the method. IsDef 4. The second parameter of the promiseSend method is the resolver method 5. The promiseSend method may accept the parameter 6

A supplement to Promises/B specifications

Promises/D specification has further detailed constraints on the methods of ref, Reject, def and defer defined in Promises/B specification. These details are omitted here.

Promises/A + specification

Promises/A/B/D specifications mentioned above are written by A group called CommonJS. Promises/A+ is an organization that calls itself Promises/A+. These specifications are based on Promises/A. Designed to improve interoperability between promise implementations.

Promises/A+ is A detailed complement to the. Then method, which defines A detailed Promise Resolution Procedure and uses the. Then method as A Promise object identification method.

Promises/A+ also offers compatibility testing tools to make sure implementations are compatible.

Implement a mini-version of the Promise

With all this specification, let’s look at how to implement a simple, short Promise.

The state machine

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise() {
  // store state which can be PENDING, FULFILLED or REJECTED
  var state = PENDING;

  // store value or error once FULFILLED or REJECTED
  var value = null;

  // store sucess & failure handlers attached by calling .then or .done
  var handlers = [];
}
Copy the code

State change

Only two state transitions are supported: FULFILL and Reject

/ /... function Promise() { // ... function fulfill(result) { state = FULFILLED; value = result; } function reject(error) { state = REJECTED; value = error; }}Copy the code

The FULFILL and Reject methods are low-level, and usually the more advanced resolve method is open to the outside world.

/ /... function Promise() { // ... function resolve(result) { try { var then = getThen(result); if (then) { doResolve(then.bind(result), resolve, reject) return } fulfill(result); } catch (e) { reject(e); }}}Copy the code

The resolve method can accept either a normal value or another promise as an argument, and if you accept a promise as an argument, wait for it to complete. A promise is not allowed to be fulfill by another promise, so you need to open the resolve method. The resolve method relies on some helper methods defined as follows:

/**
 * Check if a value is a Promise and, if it is,
 * return the `then` method of that promise.
 *
 * @param {Promise|Any} value
 * @return {Function|Null}
 */
function getThen(value) {
  var t = typeof value;
  if (value && (t === 'object' || t === 'function')) {
    var then = value.then;
    if (typeof then === 'function') {
      return then;
    }
  }
  return null;
}

/**
 * Take a potentially misbehaving resolver function and make sure
 * onFulfilled and onRejected are only called once.
 *
 * Makes no guarantees about asynchrony.
 *
 * @param {Function} fn A resolver function that may not be trusted
 * @param {Function} onFulfilled
 * @param {Function} onRejected
 */
function doResolve(fn, onFulfilled, onRejected) {
  var done = false;
  try {
    fn(function (value) {
      if (done) return
      done = true
      onFulfilled(value)
    }, function (reason) {
      if (done) return
      done = true
      onRejected(reason)
    })
  } catch (ex) {
    if (done) return
    done = true
    onRejected(ex)
  }
}
Copy the code

The recursion between resolve and doResolve is clever enough to handle layers of nesting of promises (a promise’s value is a promise).

The constructor

// ...

function Promise(fn) {
    // ...
    doResolve(fn, resolve, reject);
}
Copy the code

. The done method

// ...
function Promise(fn) {
  // ...

  function handle(handler) {
    if (state === PENDING) {
      handlers.push(handler);
    } else {
      if (state === FULFILLED &&
        typeof handler.onFulfilled === 'function') {
        handler.onFulfilled(value);
      }
      if (state === REJECTED &&
        typeof handler.onRejected === 'function') {
        handler.onRejected(value);
      }
    }
  }

  this.done = function (onFulfilled, onRejected) {
    // ensure we are always asynchronous
    setTimeout(function () {
      handle({
        onFulfilled: onFulfilled,
        onRejected: onRejected
      });
    }, 0);
  }
  // ...
}
Copy the code

Method. Then

/ /... function Promise(fn) { // ... this.then = function (onFulfilled, onRejected) { var self = this; return new Promise(function (resolve, reject) { return self.done(function (result) { if (typeof onFulfilled === 'function') { try { return resolve(onFulfilled(result)); } catch (ex) { return reject(ex); } } else { return resolve(result); } }, function (error) { if (typeof onRejected === 'function') { try { return resolve(onRejected(error)); } catch (ex) { return reject(ex); } } else { return reject(error); }}); }); } / /... }Copy the code

$.promise

Back before 1.8, jQuery’s THEN method was just A shorthand for calling done, Fail, and Progress at the same time. Promises/A’s THEN behaves more like the jQuery Pipe. JQuery 1.8 fixes this by making then a synonym for PIPE. However, due to backward compatibility issues, jQuery Promise Promises/A are not going to be very well received.

In addition, in the Promises/A specification, A Promise object generated by the THEN method is either executed or rejected depending on whether the callback called by the THEN method returns A value or throws an error. Throwing an error in the callback of JQuery’s Promise object is a bad idea because the error will not be caught.

summary

The final example shows that the key to implementing a Promise is to implement the doResolve method well, triggering a callback after the completion. To ensure asynchronous setTimeout(fun, 0); Is a key step.

Promise has always worked well, optimizing the code structure for asynchronous NodeJS processing. However, they are somewhat ignorant and curious about how it works. It took some managers to review and translate the Promise specification to fully understand the Promise details.

Refer to the reading

  1. Promises/A
  2. Promises/B
  3. Promises/D
  4. Promisejs
  5. Promises/A+
  6. As soon as possible
  7. A minimalist implementation of a javascript promise
  8. Lightweight implementation of promises
  9. How is a promise/defer library implemented?
  10. Basic Javascript promise implementation attempt
  11. You’re Missing the Point of Promises
  12. Boom! Promises/A+ Was Born
  13. Futures and promises
  14. JavaScript Promises – There and back again