Promise/Deferred standards

The Promise/Deferred pattern was abstracted as a draft proposal by Kris Zyp in 2009 and published in the CommonJS specification. As more and more applications use Promise/Deferred patterns, CommonJS has abstracted Promises/A, Promises/B, Promises/D and other typical asynchronous Promise/Deferred models.

Advantages: Alleviates the problem of callback hell to some extent.

Promises/A

The Promise/Deferred pattern actually consists of two parts, Promise and Deferred.

Promises/A offers this abstract definition of A single asynchronous operation, as shown below.

  • The Promise operation will only be in one of three states: incomplete, complete, or failed.
  • The Promise state can only be changed from an incomplete state to a completed state or a failed state, and cannot be reversed. The completed and failed states cannot convert to each other.
  • Once the state of a Promise is changed, it cannot be changed.

Promise/Deferred internal implementation

Promises/A proposals are simple by DEFINITION. All a Promise object needs is the then() method. But for the THEN () method, there are simple requirements.

  • Accept completed and error callback methods. When the operation completes or an error occurs, the corresponding method is called.
  • Optionally, the Progress event callback is supported as a third method.
  • The then() method only accepts function objects; the rest are ignored.
  • The then() method continues to return the Promise object to implement the chain call

To demonstrate Promises/A, here we try to make A simple implementation by inheriting the Events module of Node. The code is as follows:

var event = require('events') var util = require('util') var EventEmitter = event.EventEmitter var Promise= function () { EventEmitter.call(this) } util.inherits(Promise, EventEmitter); Prototype. Then = function (with ledHandler, errorHandler, progressHandler) { if(typeof fulfilledHandler === 'function') { this.once('success', fulfilledHandler); } if(typeof errorHandler === 'function') { this.once('error', errorHandler); } if(typeof progressHandler === 'function') { this.once('progress', progressHandler); } return this }Copy the code

What you see here is that what the then() method does is store the callback function. To complete the process, you also need to trigger places where these callback functions are executed. The objects that implement these functions are often referred to as Deferred objects. Example code is as follows:

Var Deferred = function () {this.state = 'undepressing '; this.promise = new Promise(); } Deferred.prototype.resolve = function (obj) { this.state = 'fulfilled'; this.promise.emit('success', obj); } Deferred.prototype.reject = function (err) { this.state = 'failed'; this.promise.emit('error', err); } Deferred.prototype.progress = function (data) { this.promise.emit('progress', data); } // CommonJS standard exports of Promise and Deferred module.exports = {Promise, Deferred}Copy the code

The corresponding relationship between the states and methods is shown in the figure below:

Difference between a Promise and a Deferred:

  • Deferred is primarily used internally to maintain the state of asynchronous models;
  • Promises are externally exposed through the then() method to add custom logic.

The overall relationship between promises and Deferred is shown below.

Advantages:

In contrast to the event publish/subscribe pattern, the Promise/Deferred pattern’s API interface and abstract model are concise.

It encapsulates the immutable parts of the business in the Deferred and gives the mutable parts to the Promise.

The basic implementation of Promise/A+

At this point, A basic Promise base that conforms to Promise/A+ is implemented

// promise-base.js

Var util = require('util') var EventEmitter = event.emitter Function () {EventEmitter. Call (this)} util.inherits(Promise, EventEmitter); Prototype. Then = function (with ledHandler, errorHandler, progressHandler) { if(typeof fulfilledHandler === 'function') { this.once('success', fulfilledHandler); } if(typeof errorHandler === 'function') { this.once('error', errorHandler); } if(typeof progressHandler === 'function') { this.once('progress', progressHandler); Var Deferred = function () {this.state = 'undepressing '; this.promise = new Promise(); } Deferred.prototype.resolve = function (obj) { this.state = 'fulfilled'; this.promise.emit('success', obj); } Deferred.prototype.reject = function (err) { this.state = 'failed'; this.promise.emit('error', err); } Deferred.prototype.progress = function (data) { this.promise.emit('progress', data); } / / generated Deferred callback function. The prototype. The callback = function () {var that = this return function (err, file) { if(err) { return that.reject(err); } that.resolve(file) } }; module.exports = { Promise, Deferred }Copy the code

Multi-asynchronous collaboration in Promises

Multiple asynchronous parallel processes

For multiple file read scenarios, ALL reabstracts two separate promises into a new Promise. A simple prototype implementation is given here. The code is as follows:

// promise-all.js

var { Promise, Deferred } = require('./promise-base') Deferred.prototype.all = function (promises) { var count = promises.length; var that = this; var results = []; promises.forEach(function(promise , i) { promise.then(function(data){ count--; results[i] = data; if(count === 0){ that.resolve(results); } }, function(err){ that.reject(err); })}); return this.promise } module.exports = { Promise, Deferred }Copy the code

Here multiple asynchronous operations are abstracted through the all() method. An asynchronous operation succeeds only if all asynchronous operations succeed, and if one of them fails, the entire operation fails.

// demo1.js

var { Deferred } = require('./promise-all')
var fs = require('fs')

var readFile = function(file, encoding) {
    var deferred = new Deferred()
    fs.readFile(file, encoding, deferred.callback())
    return deferred.promise
}

var promise1 = readFile('file1.txt', 'utf-8')
var promise2 = readFile('file2.txt', 'utf-8')

var deferred = new Deferred
deferred.all([promise1, promise2]).then(function(results){
    console.log(results)
}, function (err) {
    // error
})
Copy the code

Run Demo2 and get

Dependency handling of asynchronous calls

All () is suitable for asynchronous serial execution without dependencies, but when the result of the current one is the input of the later call, the all() method is not sufficient. Promise provides a chained invocation for this scenario. The chain call supports the promise to return a new promise each time, and the result of the response is passed to the new promsie as a callback function.

There are two main steps to getting Promise to support chained execution.

  1. Store all callbacks in the queue.
  2. When a Promise completes, the callbacks are executed one by one. Upon detecting that a new Promise object is returned, the execution is stopped, then the Promise reference of the current Deferred object is changed to the new Promise object, and the remaining callbacks in the queue are forwarded to it.

// promose-chain.js

Var Promise = function () {this.queue = []; What the this.isPromise = true} // then() method does is store the callback function. Promise.prototype.then = function (fulfilledHandler, errorHandler, progressHandler) { var handler = {}; if(typeof fulfilledHandler === 'function') { handler.fulfilled = fulfilledHandler } if(typeof errorHandler === 'function') { handler.error = errorHandler } this.queue.push(handler) return this } var Deferred = function () { this.promise = new Promise(); } / / complete state Deferred. Prototype. Resolve = function (obj) {var promise = this. Promise; var handler; While ((handler = promise.queue.shift())) {// Fetch the first element from promsie, This will be a big pity if(ret && ret.ispromise){// This will be a big pity if(ret && ret.isPromise) {// This will be a big pity if(ret && ret.isPromise) {// Queue = promise.queue; ret.queue = promise.queue; // Initialize its internal queue this.promise = ret; // Assign to the inner promise return; }}}}; / / failure state Deferred. Prototype. Reject = function (err) {var promise = this. Promise; var handler; While ((handler = promise.queue.shift())) {if(handler && handler. Error){var ret = handler Ret.queue = promise.queue; if(ret && ret.ispromise) {// If it returns the callback ret.queue = promise.queue; // Initialize its internal queue this.promise = ret; // Assign to the inner promise return; }}}}; / / generated callback function Deferred. Prototype. The callback = function () {var that = this return function (err, file) { if(err) { return that.reject(err); } that.resolve(file) } }; module.exports = { Promise, Deferred }Copy the code

// demo2.js

var fs = require('fs');
// var smooth = require('./smooth');
var { Deferred } = require('./promise-chain')

var readFile = function(file, encoding) {
    var deferred = new Deferred()
    fs.readFile(file, encoding, deferred.callback())
    return deferred.promise
}

readFile('file1.txt', 'utf8').then(function(file1) {
    return readFile(file1.trim(), 'utf8');
}).then(function(file2) {
    console.log(file2)
})
Copy the code

Run Demo2 to get

The results are as expected!

The API Promise

Again, you’ll find that a lot of preparation is required to experience a better API. Here’s a method to Promise methods in bulk:

// smooth.js const { Deferred } = require("./promise-chain") var smooth = function (method) { return function () { var Deferred = new deferred var args = Array (). The prototype. Slice. The call (the arguments, 0) / / gain parameter list: Deep copy of array (all elements from 0 to the end of array) args.push(deferred.callback()); // Construct parameters, Callback as a callback (usually as the last argument) method.apply(null, Args) // Bind to Method return deferred.promise}} module.exports = smoothCopy the code

This gives us a simple way to promsize an asynchronous method that runs as a callback function

Rewrite demo2

// import { Deferred } from 'promise-chain'
var fs = require('fs');
var smooth = require('./smooth');
var { Deferred } = require('./promise-chain')

var readFile = smooth(fs.readFile)
readFile('file1.txt', 'utf8').then(function(file1) {
    return readFile(file1.trim(), 'utf8');
}).then(function(file2) {
    console.log(file2)
})
Copy the code

Run Demo2 to get

Still running as expected!

Write in the last

Originally, an abstract definition of Promise was expressed from the standard level of Promise/A+, that is:

  • The Promise operation will only be in one of three states: incomplete, complete, or failed
  • Once the state of a Promise is changed, it cannot be changed.
  • The Promise state can only be changed from an incomplete state to a completed state or a failed state, and cannot be reversed. The completed and failed states cannot convert to each other.

Then from the Promise and Deffered parts of the realization of the corresponding part of the internal principle, handwriting to achieve a basic Promise. The main expression of the Promise/Deferred mode actually contains two parts, namely Promise and Deferred.

  • Deferred is primarily used internally, primarily where these callback functions are triggered to maintain the state of the asynchronous model;
  • Promises work externally, and what the THEN () method does is store the callback function and expose it externally through the THEN () method to add custom logic.

Then we discuss the multi-asynchronous collaboration problem in Promise, which is divided into two parts

  1. Multiple asynchronous parallel processes

Promsie provides the all method, which handles multiple asynchronous parallel processes. An asynchronous operation is considered successful only if all asynchronous operations succeed, and if one of them fails, the entire asynchronous operation fails. Promsie-all is implemented based on promise-base. The All method is implemented for the Promise extension, which basically maintains a result queue that is returned when all asynchronous processes succeed.

  1. Multiple asynchronous calls depend on processing

Typical asynchronous processing scenarios sometimes involve the handling of dependencies, and promises provide a way to make chained calls when the result of the current call is the input to a later call.

The then() method continues to return the Promise object to implement the chain call.

  • A callback queue is maintained internally and all callbacks are stored in the queue.
  • When a Promise completes, the callbacks are executed one by one. Upon detecting that a new Promise object is returned, the execution is stopped, then the Promise reference of the current Deferred object is changed to the new Promise object, and the remaining callbacks in the queue are forwarded to it.

Source address: Promise, can be compared to the code to learn the content of this article, useful words don’t forget to give a star ah ~