Type of article: Personal study notes
The premise of writing A Promise is to know the Promise/A+ specification, but the basic points need to be mastered.
1. Promise/A + specification
Promise/A+ specification
Reminder: Translation is a word for word translation using personal understanding + Google Translate
(1) Terms:
1.1. A “promise” is an object or function with then methods that conform to this behavior specification.
1.2. “thenable” is an object or function that defines the THEN method.
1.3. “value” can be any valid JavaScript value (including undefined, thenable, promise).
1.4. “exception” is the value thrown by the throw statement.
1.5. “Reason” is a value that indicates why a promise was rejected.
(2) Specification
2.1. Promise
A promise must be in one of three states: Pending, depressing, or Rejected.
2.1.1. When the Promise is in pending state:
- 2.1.1.1. There may be a pity state or rejected state
2.1.2. When the promise is fulFilled:
-
2.1.2.1. Cannot be converted to any other state.
-
2.1.2.2. There must be a value and the value cannot be changed.
2.1.3. When the Promise state is Rejected:
-
2.1.3.1. Cannot be converted to any other state.
-
2.1.3.2. There must be a reason value and the reason value cannot be changed.
In this context, “immutable” means invariant (i.e. : ===), but not immutable at a deeper level.
2.2. Then the method
A Promise must provide a THEN method to access the current value, the final value, or Reason.
A Promise’s then method takes two arguments:
promise.then(onFulfilled, onRejected)
Copy the code
2.2.1. OnFulfilled and onRejected are both optional parameters:
-
2.2.1.1. If ondepressing is not a function, it must be ignored.
-
2.2.1.2. If onRejected is not a function, it must be ignored.
2.2.2. If ondepressing is a function:
-
2.2.2.1. The promise must be called after the fulFilled fulFilled fulFilled state, and its first parameter is the promise value.
-
2.2.2.2. The promise cannot be called before the fulFilled fulFilled state.
-
2.2.2.3. It cannot be called more than once
2.2.3. If onRejected is a function:
-
2.2.3.1. You must call the Promise after the Rejected state is implemented, and its first argument is Reason.
-
2.2.3.2. You cannot call the Promise before it implements the Rejected state.
-
2.2.3.3. It cannot be called more than once
2.2.4. OnFulfilled or onRejected cannot be called before the execution context stack contains only platform code. [3.1]
2.2.5. OnFulfilled and onRejected must be called as functions (i.e. without this value). [3.2]
2.2.6. Then may be called multiple times in the same promise.
-
2.2.6.1. If the promise is a depressing state, all the respective ondepressing callbacks of THEN must be executed in the order in which they are invoked by THEN.
-
2.2.6.2. If a Promise is rejected, all then onRejected callbacks must be executed in the order in which they are invoked by THEN.
2.2.7. Then must return a Promise [3.3].
promise2 = promise1.then(onFulfilled, onRejected);
Copy the code
- 2.2.7.1. If onFulfilled or onRejected returns a value X, run the Promise solution:
[[Resolve]](promise2, x)
Copy the code
-
2.2.7.2. If onFulfilled or onRejected throws exception E, promise2 must perform Rejected and treat E as reason.
-
2.2.7.3. If ondepressing is not a function and promise1 is a depressing state, promise2 must be a depressing state with the same value of promise1.
-
2.2.7.4. If onRejected is not a function and promise1 is in the Rejected state, promise2 must be in the Rejected state with the same reason as promise1.
2.3. Promise executes the program(Solution)
The Promise resolvator is an abstract operation represented by entering a Promise and a value, which we use [[Resolve]](Promise, x). If x is a thenable, it attempts to make the Promise adopt the state of X, Let’s say X behaves like Promise. Otherwise, it carries a value X to make the promise move to a depressing state.
This treatment of Thenable allows Promises to interoperate as long as they expose A THEN method that Promises/A+ complies with. It also allows Promises/A+ implementations to “integrate” substandard implementations using sound THEN methods.
To run [[Resolve]] (promise, x), perform the following steps:
2.3.1. If a Promise and X refer to the same object, the Promise executes reject with a TypeError as Reason.
2.3.2. If X is a Promise, adopt its state [3.4] :
-
2.3.2.1. If X is pending, the promise must remain pending until X is a pity or Rejected state.
-
2.3.2.2. If X is a big pity, with the same value, make the promise become a big pity.
-
2.3.2.3. If x is the Rejected state, change the Promise state to The Rejected state with the same reason.
2.3.3. In addition, if x is an object or function:
-
2.3.3.1. Assign x. teng to THEN. [3.5]
-
2.3.3.2. If the attribute x.chen is used to raise exception E, the promise state is rejected with eas reason.
-
2.3.3.3. If then is a function, call it with x as this, with the first argument resolvePromise and the second argument rejectPromise as follows:
-
- 2.3.3.3.1. If resolvePromise is called with a value y, execute [[Resolve]](promise, y).
-
- 2.3.3.3.2. If the rejectPromise is invoked with a Reason r, reject the Promise state with r as reason.
-
- 2.3.3.3.3. If both resolvePromise and rejectPromise are called, or if multiple calls are made to the same parameter, the first call takes precedence and all other calls are ignored.
-
- 2.3.3.3.4. If calling THEN throws exception e:
-
-
- 2.3.3.3.4.1. If resolvePromise or rejectPromise has already been invoked, ignore it.
-
-
-
- 2.3.3.3.4.2. Otherwise, use eas reason to change the Promise state to Rejected.
-
2.3.4. If then is not a function, use X as value to make the promise become a fulFilled state.
If a Promise is Resolved with a Thenable in a thenable loop chain, Thus the recursive nature of [[Resolve]](promise, thenable) eventually causes [[Resolve]](Promise, thenable) to be called again, following the algorithm above will result in infinite recursion, encouraging but not required implementations, A Reject that executes a promise to detect such recursion and reason with the message TypeError. [3.6]
(3) Notes
3.1. The “platform code” here refers to the guidance engine, execution environment andpromiseImplementation code. In fact, this requirement ensures that it is called after the event loop turnonFulfilled 和 onRejectedExecute asynchronously, and then call with the new stack. You can use“Macro task”Mechanisms (e.g.,setTimeout 或 setImmediate) or“Microtasks”Mechanisms (e.g.,MutationObserver 或 process.nextTick). Due to thepromiseThe implementation is considered platform code, so it may itself contain a task scheduling queue or “trampoline” in which handlers are invoked.
3.2. That is to say, in strict mode,this 是 undefined. In the non-strict mode,thisIs a global object.
3.3. An implementation may allow if the implementation meets all requirementspromise2 === promise1. Each implementation should document whether it can be producedpromise2 === promise1And under what conditions.
3.4. Usually, onlyxFrom the current implementation, it is known that it is a truepromise. This section allows the adoption of known conformance in an implementation-specific mannerpromiseIn the state.
3.5. Store pairs firstx.thenThe process of testing the reference, and then calling the reference avoids thex.thenMultiple accesses to the property. Such precautions are important to ensure consistency of visitor attributes, whose values can change between retrievals.
3.6. The implementation should not set any limit on the depth of the chain that can be established, and assume that beyond that limit the recursion will be infinite. Only a true cycle should resultTypeError. Always recursion is the correct behavior if you encounter an infinite number of different cans.
(3) Summary
2. Write PromiseA+ spec promises
(1) Promise state
1. Pending: to be determined
This is a big pity
3. The rejected: rejected
Function writes an example
// 1. First, a Promise has three states. The initial state is pending
// 2. This is a big pity or rejected state
// 3. This is a big pity or rejected state.
// There must be a value (pity),rejected is reason.
// 4.Promise receives a callback
function MyPromise(exector) {
const PENDING = 'pending';
const FULFILLED = 'fulFilled';
const REJECTED = 'rejected';
const that = this;
that.status = PENDING;
function resolve(value) {
// This is a big pity. This is a big pity. // This is a big pity
if(that.status === PENDING) { that.status = FULFILLED; that.value = value; }}function reject(reason) {
// The reason is the same as above
if(that.status === PENDING) { that.status = REJECTED; that.value = reason; }}try {
exector(resolve, reject);
} catch(error) { reject(error); }}Copy the code
If no judgment is made in the resolve and Reject methods above, the following condition changes the promise state again:
let p = new MyPromise((resolve, rejrect) = > {
reject('refuse');
resolve('address');
})
console.log(p);
Copy the code
This is A big pity. If there is no judgment, the final state will be fulfilled, and the state after the judgment is rejected, which will not be changed again. In this way, the Promise/A+ specification can be fulfilled.
Class to write an example
class MyPromise {
static PENDING = 'pending';
static FULFILLED = 'fulFilled';
static REJECTED = 'rejected';
constructor(executor) {
this.status = MyPromise.PENDING;
this.value = null;
try {
// this. Resolve just gets the resolve method, not this
// Is actually called by global objects, but the default is strict mode in class
// If you do not change the inner this pointer of resolve, its this is undefined
executor(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error)
}
}
resolve(value) {
if (this.status === MyPromise.PENDING) {
this.status = MyPromise.FULFILLED;
this.value = value; }}reject(reason) {
if (this.status === MyPromise.PENDING) {
this.status = MyPromise.REJECTED;
this.reason = reason; }}}/ / test
let p = new MyPromise(function (resolve, reject) {
reject('XKHM')});console.log(p);
Copy the code
2. Then method
promise.then(onFulfilled, onRejected)
Copy the code
1. OnFulfilled and onRejected are both optional parameters of the then method. If these two parameters are not functions, they will be ignored.
Ondepressing must be called when the Promise is fulFilled and accepts a parameter value, which will be called at most once
3. OnRejected must be called when the Promise executes the rejected state and accepts a parameter, Reason, and is called at most once
4. OnFulfilled and onRejected need to be called in the next event loop.
5. OnFulfilled and onRejected need to be called as a function (there is no this value of its own)
6. The then method can be invoked by the Promise chain
The then method must return a Promise object
Remind:
(1) Then needs to deal with three states of Promise, including pending state
(2) If then itself returns a Promise without processing, it needs to be processed separately
3.Promise resolution process
The Promise resolver is an abstract operation that takes a Promise and a value, which we express as [[Resolve]](Promise, x)
1. If a Promise and X point to the same object, reject the Promise as TypeError
2. If X is a promise, accept its status
3. Or x is an object or function
The sample
class MyPromise {
static PENDING = 'pending';
static FULFILLED = 'fulFilled';
static REJECTED = 'rejected';
constructor(executor) {
this.status = MyPromise.PENDING;
this.value = null;
this.callback = [];
try {
// this. Resolve just gets the resolve method, not this
// Is actually called by global objects, but the default is strict mode in class
// If you do not change the inner this pointer of resolve, its this is undefined
executor(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error)
}
}
resolve(value) {
if (this.status === MyPromise.PENDING) {
this.status = MyPromise.FULFILLED;
this.value = value;
setTimeout(_= > {
this.callback.forEach(callFn= >{ callFn.onFulfilled(value) }); }}})reject(value) {
if (this.status === MyPromise.PENDING) {
this.status = MyPromise.REJECTED;
this.value = value;
setTimeout(_= > {
this.callback.forEach(callFn= >{ callFn.onRejected(value) }); }}})then(onFulfilled, onRejected) {
if (typeofonFulfilled ! = ='function') {
onFulfilled = value= > value;
}
if (typeofonRejected ! = ='function') {
onRejected = value= > value;
}
let promise = new MyPromise((resolve, reject) = > {
if (this.status === MyPromise.PENDING) {
this.callback.push({
onFulfilled: value= > {
this.parse(promise, onFulfilled(this.value), resolve, reject)
},
onRejected: value= > {
this.parse(promise, onRejected(this.value), resolve, reject)
}
})
}
if (this.status === MyPromise.FULFILLED) {
setTimeout(() = > {
this.parse(promise, onFulfilled(this.value), resolve, reject)
})
}
if (this.status === MyPromise.REJECTED) {
setTimeout(() = > {
this.parse(promise, onRejected(this.value), resolve, reject)
})
}
});
return promise;
}
parse(promise, res, resolve, reject) {
if (promise == res) {
throw new TypeError('Then can't return the same promise as then.');
}
try {
if (res instanceof MyPromise) {
res.then(resolve, reject)
} else {
resolve(res)
}
} catch (error) {
reject(error)
}
}
}
Copy the code
Final complete example
class MyPromise {
static PENDING = 'pending';
static FULFILLED = 'fulFilled';
static REJECTED = 'rejected';
constructor(executor) {
this.status = MyPromise.PENDING;
this.value = null;
this.callback = [];
try {
// this. Resolve just gets the resolve method, not this
// Is actually called by global objects, but the default is strict mode in class
// If you do not change the inner this pointer of resolve, its this is undefined
executor(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error)
}
}
resolve(value) {
if (this.status === MyPromise.PENDING) {
this.status = MyPromise.FULFILLED;
this.value = value;
setTimeout(_= > {
this.callback.forEach(callFn= >{ callFn.onFulfilled(value) }); }}})reject(value) {
if (this.status === MyPromise.PENDING) {
this.status = MyPromise.REJECTED;
this.value = value;
setTimeout(_= > {
this.callback.forEach(callFn= >{ callFn.onRejected(value) }); }}})then(onFulfilled, onRejected) {
if (typeofonFulfilled ! = ='function') {
onFulfilled = value= > value;
}
if (typeofonRejected ! = ='function') {
onRejected = value= > value;
}
let promise = new MyPromise((resolve, reject) = > {
if (this.status === MyPromise.PENDING) {
this.callback.push({
onFulfilled: value= > {
this.parse(promise, onFulfilled(this.value), resolve, reject)
},
onRejected: value= > {
this.parse(promise, onRejected(this.value), resolve, reject)
}
})
}
if (this.status === MyPromise.FULFILLED) {
setTimeout(() = > {
this.parse(promise, onFulfilled(this.value), resolve, reject)
})
}
if (this.status === MyPromise.REJECTED) {
setTimeout(() = > {
this.parse(promise, onRejected(this.value), resolve, reject)
})
}
});
return promise;
}
parse(promise, res, resolve, reject) {
if (promise == res) {
throw new TypeError('Then can't return the same promise as then');
}
try {
if (res instanceof MyPromise) {
res.then(resolve, reject)
} else {
resolve(res)
}
} catch (error) {
reject(error)
}
}
static resolve(value) {
return new MyPromise((resolve, reject) = > {
if (value instanceof MyPromise) {
value.then(resolve, reject);
} else{ resolve(value); }})}static reject(reason) {
return new MyPromise((_, reject) = >{ reject(reason); })}static all(promises) {
let resolves = [];
return new MyPromise((resolve, reject) = > {
promises.forEach((promise, index) = > {
promise.then(value= > {
resolves.push(value)
if(resolves.length === promises.length) { resolve(resolves); }},reason= >{ reject(reason); })})})}static race(promises) {
return new MyPromise((resolve, reject) = > {
promises.forEach(promise= > {
promise.then(value= > {
resolve(value)
})
})
})
}
}
Copy the code
4. Test
Use the Promise/A+ testing tool
npm install promises-aplus-tests -g
Copy the code
npx promises-aplus-tests promise.js
Copy the code
5. Remind
(1) The Promise internal error was swallowed, by whom, by reject
Illustrative example
const pending = 'pending';
const fulFilled = 'fulFilled';
const rejected = 'rejected';
const resolveFn = [];
const rejectFn = [];
/ / Promise/A + specification
// 1. Promise state
// (1) When a Promise is in a pending state, it can be switched to two other states
// (2) When the Promise is a big pity, it cannot be changed, and there must be a value that cannot be changed
// (3) When the Promise is rejected, it cannot be changed, and there must be a reason value which cannot be changed
function MyPromise(execute){
const that = this;
that.status = pending;
function resolve(value){
if(that.status === pending){ that.status = fulFilled; that.value = value; }}function reject(reason){
if(that.status === pending){ that.status = rejected; that.value = reason; }}// If there is an error, reject is executed immediately
// This means that errors in promises are swallowed up by reject
// try... Catch is not to be caught
try {
execute(resolve,reject);
} catch (error) {
reject(error)
}
}
/ / 2. Then method
// First, the then method is a method that hangs on the Promise prototype
// The (1) then method receives two callback functions, if not ignored
// (2) then returns a Promise
// The then method needs to be executed asynchronously
// (4) then can be called chain
// (5) If onResolve is a function, it must be called after the Promise state is fulFilled
// (6) If onRejected is a function, it must be called after the Promise state is Rejected
// (7) Then does not have its own this.
MyPromise.prototype.then = function(onResolve,onRejected){
if(typeofonResolve ! = ='function') {return onResolve = value= > value
}
if(typeofonRejected ! = ='function') {return onRejected = reason= > reason
}
let that = this;
let p = new MyPromise((resolve,reject) = > {
if(that.status === fulFilled){
setTimeout(_= > {
onResolve(that.value)
})
}
if(that.status === rejected){
setTimeout(_= > {
onRejected(that.value)
})
}
})
return p;
}
/ / test
try {
let p = new MyPromise((resolve,reject) = > {
throw new TypeError('Throw an error')})console.log(p);
} catch (error) {
console.log(error);
}
// p.then();
try {
let p1 = new Promise(resolve= > {
throw new TypeError('Throw an error')})console.log(p1);
} catch (error) {
console.log(error);
}
Copy the code
When a Promise instance calls THEN, it will be devoured by reject if an error is thrown in the then callback
6. The meaning of handwritten promises
Equivalent to according to a specification and then use code area implementation, equivalent to in-depth understanding of the source code, but the Promise source code is only more than 100 lines, calculate reading a small project source.
Handwritten Promise complete code
const pending = 'pending';
const fulFilled = 'fulFilled';
const rejected = 'rejected';
/ / Promise/A + specification
// 1. Promise state
// (1) When a Promise is in a pending state, it can be switched to two other states
// (2) When the Promise is a big pity, it cannot be changed, and there must be a value that cannot be changed
// (3) When the Promise is rejected, it cannot be changed, and there must be a reason value which cannot be changed
function MyPromise(execute){
const that = this;
that.status = pending;
that.resolveFn = [];
that.rejectFn = [];
function resolve(value){
setTimeout(_= > {
if(that.status === pending){
that.status = fulFilled;
that.value = value;
that.resolveFn.forEach(f= > {
f(that.value)
})
}
})
}
function reject(reason){
setTimeout(_= > {
if(that.status === pending){
that.status = rejected;
that.value = reason;
that.rejectFn.forEach(f= > {
f(that.value)
})
}
})
}
// If there is an error, reject is executed immediately
// This means that errors in promises are swallowed up by reject
// try... Catch is not to be caught
try {
execute(resolve,reject);
} catch (error) {
reject(error)
}
}
/ / 2. Then method
// First, the then method is a method that hangs on the Promise prototype
// The (1) then method receives two callback functions, if not ignored
// (2) then returns a Promise
// The then method needs to be executed asynchronously
// (4) then can be called chain
// (5) If onResolve is a function, it must be called after the Promise state is fulFilled
// (6) If onRejected is a function, it must be called after the Promise state is Rejected
// (7) Then does not have its own this.
MyPromise.prototype.then = function(onResolve,onRejected){
if(typeofonResolve ! = ='function'){
onResolve = value= > value
}
if(typeofonRejected ! = ='function'){
onRejected = reason= > {
throw reason
}
}
const that = this;
let promise;
if(that.status === fulFilled){
promise = new MyPromise((resolve,reject) = > {
setTimeout(_= > {
try {
let x = onResolve(that.value);
resolvePromise(promise,x,resolve,reject)
} catch (error) {
reject(error)
}
})
})
}
if(that.status === rejected){
promise = new MyPromise((resolve,reject) = > {
setTimeout(_= > {
try {
let x = onRejected(that.value);
resolvePromise(promise,x,resolve,reject)
} catch (error) {
reject(error)
}
})
})
}
if(that.status === pending){
promise = new MyPromise((resolve,reject) = > {
that.resolveFn.push(_= > {
try {
let x = onResolve(that.value);
resolvePromise(promise,x,resolve,reject)
} catch (error) {
reject(error)
}
})
that.rejectFn.push(_= > {
try {
let x = onRejected(that.value);
resolvePromise(promise,x,resolve,reject)
} catch (error) {
reject(error)
}
})
})
}
return promise;
}
// 3.Promise executes the program
// 1. Promise and x refer to the same object, reject()
// 2. X is the promise state
// 3. X is an object or function, handled
// 4. Then is not a function
function resolvePromise(promise,x,resolve,reject){
if(promise === x){
return reject(new TypeError('Promise can't be equal to x'))}if(x instanceof MyPromise){
if(x.status === fulFilled){
resolve(x.value)
}else if(x.status === rejected){
reject(x.value)
}else{
x.then(y= > {
resolvePromise(promise,y,resolve,reject)
},reject)
}
}else if(x ! = =null && (typeof x === 'object' || typeof x === 'function')) {let executed;
try {
let then = x.then;
if(typeof then === 'function'){
then.call(x,y= > {
if(executed){
return false
}
executed = true;
resolvePromise(promise,y,resolve,reject)
},e= > {
if(executed){
return false
}
executed = true; reject(e); })}else{
resolve(x)
}
} catch (error) {
if(executed){
return false
}
executed = true; reject(error); }}else{
resolve(x)
}
}
Copy the code