A simple introduction
What Promise is, I believe it goes without saying that people who have written JS have been exposed to it at some point. When I first used Promise, I felt strange writing this way, but as I got familiar with it, I found everything was so harmonious.
Promise, as I understand it, addresses the nesting of asynchronous callbacks and represents a consequence of asynchronous operations. It is a state machine, a callback wrapper in terms of implementation (if you disagree with your own understanding, please discuss it).
After really looking at the Promise/A+ specification and other people’s implementation code, I found that the principle and implementation of Promise is not very difficult, so I have this article, which aims to learn from myself and may also help some people. This is the complete code and github address of the final Promise implementation. I will be updating frequently to learn in-depth articles on modern JS frameworks. Welcome star.
Promise specification
Promise has been available in various versions of “Promise” since before ES6, but when it comes to using them all together, it can be disastrous without a unified implementation standard. So there’s the specification, and Promise/A+ is Promise’s current specification. Specifications are intended to help implementers specify goals to achieve, not how to achieve them. Once you’ve written your Promise, to determine if it meets the Promise/A+ specification, you can use Promises -Tests.
Principles and Implementation
Let’s start by thinking about what the implementation of promises should look like. Promise encapsulates and hides our asynchronous callbacks, allowing us to chain callbacks and error handling into then functions, so the normal idea would be to store the callbacks in THEN and call them by Promise when the asynchro is done.
The first is the Promise constructor
function Promise (executor) {
// To prevent this from being corrupted for aspect subsequent function calls
var self = this;
this.status = 'pending';
this.value = null;
this.reason = null;
// Resolve callback queue
this.resolveCbs = [];
// reject callback queue
this.rejectCbs = [];
// Change the state to resolve, save the value passed in, and invoke the appropriate callback queue
function resolve(value) {
if (self.status === 'pending') {
// Since the promise needs to be executed asynchronously, setTimeout is used to delay execution
setTimeout(function() {
self.status = 'fulfilled';
self.value = value;
self.resolveCbs.forEach(cb= >cb(value)); }); }}/// Resolve is the same as resolve, but it saves the cause and changes the state to Rejected
function reject(reason) {
if (self.status = 'pending') {
setTimeout(function() {
self.status = 'rejected';
self.reason = reason;
self.rejectCbs.forEach(cb= > cb(reason));
});
}
}
executor(resolve, reject);
}
Copy the code
Promise has three states, which are pending, depressing and Rejected. Its state can be changed from Pending to depressing and from Pending to Rejected. Once the state is determined, it cannot be changed.
With the constructor, let’s implement the key function in Promise, the then function. To implement the chained call, we need to return a Promise object, but we can’t return ourselves because the state of the Promise cannot be changed, and we need to return a new Promise object. The basic explanation is contained in the comments
Promise.prototype.then = function (onResolved, onRejected) {
var self = this;
var promise = null;
// onResolved is optional and will pass through the received value once it does not exist or is not a function
onResolved = typeof onResolved === 'function' ? onResolved : function (value) { return value; };
// onRejected is optional, and continues its error when it does not exist or is not a function
onRejected = typeof onRejected === 'function' ? onRejected : function (error) { throw error; };
// The new promise state needs to be specific to x
function resolvePromise(promise, x, resolve, reject) {
// This is part of the Resolution Procedure section of the Promise/A+ specification
// 2.3.1: If the Promise object and X reference the same object, reject the promise with a TypeError error
// If two objects are the same object, then an infinite loop will be called, and an error will occur
if (promise === x) {
return reject(new TypeError('Chaining cycle detected for promise! '));
}
// 2.3.2: If x is a promise, the following should be used to determine its state
if (x instanceof Promise) {
// 2.3.2.1: If x is pending, then the promise must be pending until x is fulfillded or Rejected
// 2.3.2.2: If x is a pity state, then the promise needs to use the same value to resolve
// 2.3.2.3: If x is rejected, then promise needs the same reason reject
if (x.status === 'pending') {
x.then(function(value) {
// Since x may still be a promise, this is called recursively
resolvePromise(promise, value, resolve, reject);
}, reject);
} else {
x.then(resolve, reject);
}
return;
}
// 2.3.3: If x is an object or function, thenable is an object or function with then functions
// 2.3.4: If x is neither an object nor a function, use x directly to resolve the promise
if((x ! = =null && typeof x === 'object') | |typeof x === 'function') {
var isCalled = false;
try {
// 2.3.3.1: assigns x. teng to THEN
var then = x.then;
// 2.3.3.2: Reject x. Chen if x. Chen is rejected
// 2.3.3.3: If then is a function, use x as this, with resolvePromise as the first argument and rejectPromise as the second
if (typeof then === 'function') {
// 2.3.3.3.1: If resolvePromise is called with a parameter value y, execute [[Resolve]](promise, y)
// 2.3.3.3.2: If rejectPromise is invoked with a reason r, reject the promise with R
then.call(x, function (y) {
// 2.3.3.3.3: If resolvePromise and rejectPromise are called at the same time, or if they are called multiple times with the same arguments, then only the first one will be executed and the rest will be ignored
if (isCalled) return;
isCalled = true;
return resolvePromise(promise, y, resolve, reject);
}, function (r) {
if (isCalled) return;
isCalled = true;
return reject(r);
});
} else {
// 2.3.3.4: Resolve promise with x if then is not a functionresolve(x); }}catch(err) {
// 2.3.3.3.4: An error is raised when calling then
// 2.3.3.3.4.1: If resolvePromise and rejectPromise have already been invoked, ignore them
// 2.3.3.3.4.2: Otherwise use ERR, reject promise
if (isCalled) return;
isCalled = true; reject(err); }}else{ resolve(x); }}function handlePromise(modifier, resolve, reject) {
return function (value) {
setTimeout(function() {
try {
var x = modifier(value);
resolvePromise(promise, x, resolve, reject);
} catch(err) { reject(err) } }); }}if (self.status === 'fulfilled') {
promise = new Promise(function (resolve, reject) {
handlePromise(onResolved, resolve, reject)(self.value);
});
} else if (self.status === 'rejected') {
promise = new Promise(function (resolve, reject) {
handlePromise(onRejected, resolve, reject)(self.reason);
});
} else {
promise = new Promise(function (resolve, reject) {
self.resolveCbs.push(handlePromise(onResolved, resolve, reject));
self.rejectCbs.push(handlePromise(onRejected, resolve, reject));
});
}
return promise;
}
Copy the code
At this point our Promise has almost been fulfilled. Let’s test whether our Promise meets the Promise/A+ specification.
test
Here we use Promises – Tests, which require an adapter to be implemented. Let’s implement one according to its requirements
Promise.deferred = function () {
var global = {};
var promise = new Promise(function (onResolve, onReject) {
global.onResolve = onResolve;
global.onReject = onReject;
});
var resolve = function (value) {
global.onResolve(value);
};
var reject = function (reason) {
global.onReject(reason);
}
return {
promise,
resolve,
reject
}
}
Copy the code
And then we export our Promise
module.exports = Promise;
Copy the code
Call our Promise to test
var promisesAplusTests = require('promises-aplus-tests');
var adapter = require('./promise');
promisesAplusTests(adapter);
Copy the code
Finally, regardless of performance, all of our promises passed the Promise/A+ tests.
Other articles
- Principle and implementation of single routing
- That’s all it takes to cross domains
- Daily Curiosity – See how ES6 classes are implemented
- React is implemented from zero
- Font size for inline CSS elements
- How to understand Genrator