Promise is an asynchronous solution. Its emergence solves the problem of using callback function to process asynchronous logic and cause callback hell. This paper mainly analyzes Promise from the basic usage of Promise, Promise A+ and handwritten Promise.
I Promise
The meaning of the Promise
Promise objects have two states:
- The state of an object is unaffected by external influences. Promise has three states:
This is a big pity (which is pending), which must be fulfilled (fulfilled) and rejected (failed).
Only the result of an asynchronous operation can determine which state it is in. No other operation can change this state. - Once the state changes, it doesn’t change again, and you can get this at any other time. A Promise’s state can change in only two ways:
pending -> fulfilled
orpending -> rejected
Promise method
- Promise is a constructor that needs to be used
new
Keyword generation instance; - The Promise has an executor that executes the function immediately, each time it is new (more on that later in handwriting);
- Executor takes two parameters
resolve
andreject
Method: resolve indicates a successful callback, reject indicates a failed callback. - Promise.prototype.then() : The first argument of the then method is passed in after the resolve and reject methods
onFulfilled
Represents the callback to resolve on success, the second argument to the then methodonRejected
Represents a callback when reject fails. Both callback functions are optional; - Promise.prototype. Catch () : null then(rejection) or.then(undefined, rejection)
- Promise. Prototype. Finally () method: whatever Promise the object’s state, will eventually perform this method;
- Promise.all() : used to wrap multiple Promise instances into new Promise instances, such as const p = promise.all ([p1, p2, p3]); This is a big pity. Only when the states of P1, P2 and P3 are programmed with depressing, can the P state be programmed with depressing. At this time, the results returned by P1, P2 and P3 form an array and are passed to the callback function of P. If one of the states is Rejected, the rejected instance is returned.
- Promise.race() method: Like promise.all () method, multiple Promise instances are assembled into a new Promise instance, but as long as the state of any one of them changes, the return value of the first changed Promise instance is the returned callback function;
- Promise.allsettled () method: Accept a group of Promise instances as parameters and package them into a new Promise instance. The package will end only after all parameters return the result, no matter the state is pity or Rejected.
- Promise. Any () method: Receive a group of Promise instances as parameters and package them into a new Promise instance. As long as the state of one instance becomes a big pity, the packaging instance will become a big pity; If all parameter instances are Rejected, the status of the wrapper function changes to Rejected.
- Promise.reslove() method: a. If the argument is an instance of a Promise, the Promise is returned unchanged; B. If the parameter is
thenable
Object, that is, havethen
Method object, which then becomes a Promise object and executes the then method immediately; C. If there is no then method or no object, the promise.resolve () method returns a new Promise object and returns the argument to the callback; D. If no parameters, return an Resolved Promise object. - Promise.reject() : returns a new Promise object in the rejected state;
The above only briefly introduces the methods that exist in the Promise, and there is no specific example to introduce how each method is used. If you do not understand, you can see the Promise of Teacher Ruan Yifeng. When I first learned about promises, I had a lot of questions: How does the underlying Promise implement asynchronous operations? The promise.then () method is a microtask. How is this implemented? For those of you who don’t know eventLoop, check it out online and there are plenty of articles about it.
Second, the Promise A +
Before writing Promise, let’s talk about what is Promise A+. The underlying implementation specification of Promise relies on Promise A+ to realize.
A promise represents the end result of an asynchronous operation. The primary way to interact with a promise is through its then method, which registers callbacks to receive the final value of a promise or the reason why a promise cannot be fulfilled.
The specification details the behavior of the THEN method and provides an interoperable foundation on which all Promises/A + compatible Promise implementations can rely. Therefore, the specification should be considered very stable. Although Promises/A + organizations will sometimes revise this specification with minor backward compatible changes to address newly discovered extremes, we will only integrate large or backward incompatible changes after careful consideration, discussion, and testing.
Historically, Promises/A + clarifies the act clause of early Promises/A proposals by expanding it to cover actual acts and omits unspecified or problematic parts.
Finally, the core Promises/A + specification doesn’t deal with how to make, keep, or reject Promises. Instead, it focuses on providing an interoperable then approach. Future work in accompanying specifications may address these topics.
1. The term
1.1. There is a then method in “Promise”, which is a function or object that conforms to this canonical behavior; 1.2. “thenable” defines the objects and functions of the THEN method 1.3. “value” represents any valid JavaScript value (including undefined, Ableable or promise) 1.4. “Exception” represents the value thrown by the throw 1.5. “Reason” represents the reason why the promise was rejected
2. Necessary conditions
2.1 Promise state
Promise must be one of these three states: Pending, fulfilled and rejected. This is a big pity.
2.1.1. When a Promise is pending:
2.1.1.1. It may become a pity state or the Rejected state.
2.1.2. When a promise is fulfilled:
2.1.2.1. There must be no transition to any other state
2.1.2.2. There must be a value that cannot be changed
2.1.3 when a promise is in the Rejected state:
2.1.3.1. There must be no transition to any other state
2.1.3.2. There must be a cause that cannot be changed
Here, “must not be changed” implies an immutable identity (e.g. ===), but it does not imply immutability of depth.
2.2 then method
A Promise must provide a THEN method to access the final value or cause.
Promise’s then method accepts 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. It must be called after the promise state is fulfilled, and the promise value is its first parameter.
2.2.2.2. It must not be called before the state of the promise becomes a pity.
2.2.2.3. It must not be called more than once.
2.2.3. If onRejected is a function
2.2.3.1. It must be called after the Promise changes to Rejected, with reason, the promise result, as its first argument.
2.2.3.2. It must not be called before the Promise state changes to Rejected.
2.2.4. OnFulfilled or onRejected must not be called before only the platform code is included in the implementation context stack [3.1]
OnFulfilled and onRejected must be called as a function (without this value) [3.2]
2.2.6. The THEN method can be called multiple times on the same promise
2.2.6.1. If a promise is a big pity, all corresponding onFulfilled callbacks must be executed in the order in which they originally called THEN
2.2.6.2. If the promise is Rejected, all corresponding onRejected callbacks must be executed in the order in which they originally called THEN
2.2.7. Then must return a Promise [3.3]
promise2 = promise1.then(onFulfilled, onRejected);
Copy the code
2.2.7.1. If ondepressing or onRjected returns a value x, run the Promise settlement [Resolve]
2.2.7.2. If onFulfilled or onRejected throws an exception E, promise2 must use E as the reason to be rejected
2.2.7.3. If ondepressing is not a function and promise1 is resolved, promise2 must be resolved with the same value as promise1
2.2.7.4. If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason as promise1
2.3 Promise Solution
The Promise resolver is an abstract operation that takes as input a promise and a value, which we represent as [[Resolve]](Promise, x). If X is a Thenable, it tries to make the Promise adopt x’s state and assumes that X behaves at least somewhat like promise. Otherwise, it will resolve the promise with the value x.
This thenable feature makes the Promise implementation more generic: as long as it exposes A THEN method that follows the Promise/A+ protocol. This also allows implementations that follow the Promise/A+ specification to coexist well with less formal but usable implementations.
To run [[Resolve]](promise, x), perform the following steps:
2.3.1. If a Promise and X reference the same object, reject the Promise with a TypeError as the reason
2.3.2. If X is a Promise, adopt its state: [3.4]
If X is pending, the Promise must remain in the pending state until X becomes a pity or rejected state 2.3.2.2. This is very depressing. If X is fulfilled, the promise 2.3.2.3 will be solved with the same value. If x is Rejected, use the same reason to reject the promise
Otherwise, if x is an object or function
2.3.3.1. Make THEN x.teng. [3.5]
2.3.3.2. If retrieving the attribute x.teng causes an exception E to be thrown, reject the promise with e as the reason
2.3.3.3. If then is a function, call it with x as this. The then method takes two callbacks, the first called resolvePromise and the second called rejectPromise:
2.3.3.3.1. If resolvePromise is called with a value y, run [[Resolve]](promise, y).
2.3.3.3.2. If rejectPromise is invoked with a reason r, reject the promise with r.
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 subsequent calls are ignored.
2.3.3.3.4. If calling THEN throws an exception e
2.3.3.4.1. If resolvePromise or rejectPromise has already been invoked, ignore it
2.3.3.4.2. Otherwise, use E as the reason to reject promise
2.3.3.4. If then is not a function, solve the promise with x
2.3.4. If x is not an object or function, use x to resolve the promise
If a promise is resolved with a cyclic thenable chain, the recursive nature of [[Resolve]](promise, thenalbe) will eventually cause [[Resolve]](promise, thenable) to be called again, Following the algorithm above will result in infinite recursion. The specification does not mandate handling such cases, but it does encourage implementors to detect the presence of such recursions and reject promises with an informative TypeError as a reason. [3.6]
annotations
3.1. Here “platform code” means the engine, environment, and promise implementation code. In practice, this needs to ensure that the onFulfilled and onRejected are executed asynchronously and should be executed with the new execution stack after the event loop in which the THEN method is called. This can be done with a “macro task” mechanism such as setTimeout or setImmediate, or with a “microtask” mechanism such as MutationObserver or process.nexttick. Because the promise implementation is considered “platform code,” it may already contain a task scheduling queue when its own handler is invoked.
3.2. In strict mode, this will be undefined; In non-strict mode, this will be a global object.
3.3. Allow promisE2 === PROMISE1 if all requirements are implemented. Each implementation should document whether or not promisE2 === promise1 is produced and under what circumstances promisE2 === PROMISe1 occurs.
3.4. In general, x is not known to be a real promise until it comes from the current implementation. This rule allows those special implementations to adopt a state of Promise that meets known requirements.
3.5. The program stores a reference to x. teng, tests that reference, and then calls that reference, avoiding multiple access to the x. teng property. Such precautions are important to ensure the consistency of visitor attributes, which can change in value between searches.
3.6. The implementation should not impose arbitrary limits on the depth of the Thenable chain and assume that beyond that arbitrary limit it will recurse indefinitely. Only real loops should raise a TypeError; If an infinite loop is encountered with Thenable, always performing recursion is the correct behavior.
Three, simulation Promise implementation
Promise.then() methods are asynchronous tasks, and as mentioned in Promise A+, can be implemented using “macro tasks” mechanisms like setTimeout or setImmediate, It can also be implemented using a “microtask” mechanism such as MutationObserver or process.nexttick. The Promise implementation is based on the V8 engine, promise.then() is a microtask, so the following simulation code creates a microtask using queueMicrotask().
Here’s an example:
// Execute the function immediately
function executor(resolve, reject) {
// Successful callback
resolve("success");
// Failed callback
reject("error");
}
const promise = new Promise(executor);
promise.then(
/ / onFulfilled function
(value) = > console.log(1, value), // 1 success
/ / onRejected function
(reason) = > console.log(2, reason)
);
// Output 1 success
Copy the code
According to the results of the example and the rule analysis of Promise A+, the following results are obtained:
- Promise has three states:
This is a big pity (which is pending), which must be fulfilled (fulfilled) and rejected (failed).
;- A Promise’s state can change in only two ways:
pending -> fulfilled
orpending -> rejected
;- Promise has an executor function that executes immediately, and two callbacks, resolve and Reject, that change the state.
- The then method is used to access the final successful value or the cause of the failure, if the state is successful call successful callback, if it is failure call failed callback;
1. Create MyPromise classes and resolve and Reject methods
class MyPromise {
constructor(executor) {
// Execute the function immediately
executor();
}
/** the arrow function is used for this reason: 2. The new operator creates an empty JavaScript object as the context for this. 3. The arrow function does not create an execution context for this, which is an instance of MyPromise */
// Change the status after success
resolve = () = > {};
// Change the failed state
reject = () = > {};
}
Copy the code
2. MyPromise state management
const PEDDING = "pedding";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class MyPromise {
// Initialize the storage state. The default value is pending
status = PEDDING;
// Initialize the resolve parameter value
value = null;
// Initialize reject parameter reason
reason = null;
// constructor
constructor(executor) {
// Execute the function immediately
executor(this.resolve, this.reject);
}
/** the arrow function is used for this reason: 2. The new operator creates an empty JavaScript object as the context for this. 3. The arrow function does not create an execution context for this, which is an instance of MyPromise */
// Change the status after success
resolve = (value) = > {
// If the state is pending, set the state to depressing and cache the value
if (this.status === PEDDING) {
// This is a big pity
this.status = FULFILLED;
// Cache successful values
this.value = value; }};// Change the failed state
reject = (reason) = > {
// If the state is pending, set it to Rejected and cache the failure cause
if (this.status === PEDDING) {
// Change the state to Rejected
this.status = REJECTED;
// Cache failure cause
this.reason = reason; }}; }Copy the code
3. Then method implementation
// ...
class MyPromise {
/ /...
/ / then method
then(onFulfilled, onRejected) {
// If the status is successful, the callback is successfully executed; if the status is failed, the callback is failed
if (this.status === FULFILLED) {
// Call the success callback to return the success value
onFulfilled(this.value);
} else if (this.status === REJECTED) {
// Call the failure callback to return the cause of failure
onRejected(this.reason); }}}Copy the code
Test it out:
const promise = new MyPromise((resolve, reject) = > {
resolve("success");
reject("error");
});
promise.then(
(value) = > console.log("resolve", value),
(reason) = > console.log("reject", reason)
);
// Output: resolve success
Copy the code
That’s right. Go ahead.
4. The asynchronous logic and then methods in the Promise are called multiple times
The above implementation does not have asynchronous logic handling. What happens if asynchronous logic is added?
const promise = new MyPromise((resolve, reject) = > {
setTimeout(() = > {
resolve("success");
reject("error");
});
});
promise.then(
(value) = > console.log("resolve", value),
(reason) = > console.log("reject", reason)
);
// Output: nothing!
Copy the code
Why does this happen? As mentioned earlier in eventLoop, js creates execution contexts (global, function, and Eval execution contexts) at compile time. Execution contexts are stored in the execution context stack (execution stack). Es6 lets and const block-level scopes are stored in the lexical environment. While asynchronous tasks are stored in the task queue, setTimeout is used here. It is a macro task that needs to wait for the completion of the task in the call stack before executing the code in the macro task.
Analyze the above code:
The main thread executes the code immediately, setTimeout is the macro task, which is stored in the macro task queue, then method executes immediately in the execution stack, and status is pending, which terminates. If resolve is removed from the macro task, there is no code behind it, so it will not return.
So how to solve it? The data can be cached using a queue (first-in, first-out) data structure. When the THEN method is executed, the pending callback is cached, and when the resolve or Reject method is called, the callback cache is determined to see if there is a callback cache in the cache, and if so, it is executed. As follows:
Two new arrays are added to MyPromise
// The callback cache was successfully initialized
onFulfilledCallback = [];
// Failed to initialize the callback cache
onRejectedCallback = [];
Copy the code
The then method callback is stored in an array
/ / then method
then(onFulfilled, onRejected) {
// If the status is successful, the callback is successfully executed; if the status is failed, the callback is failed
if (this.status === FULFILLED) {
// Call the success callback to return the success value
onFulfilled(this.value);
} else if (this.status === REJECTED) {
// Call the failure callback to return the cause of failure
onRejected(this.reason);
} else {
// Cache the current success callback and queue it to success
this.onFulfilledCallback.push(onFulfilled);
// Cache the current failed callback and queue it to failure
this.onRejectedCallback.push(onRejected); }};Copy the code
Resolve and Reject perform cached callbacks
// Change the status after success
resolve = (value) = > {
// If the state is pending, set the state to depressing and cache the value
if (this.status === PEDDING) {
// This is a big pity
this.status = FULFILLED;
// Cache successful values
this.value = value;
// If a value exists in the cache callback, execute
while (this.onFulfilledCallback.length) {
// The callback is queued
this.onFulfilledCallback.shift()(value); }}};// Change the failed state
reject = (reason) = > {
// If the state is pending, set it to Rejected and cache the failure cause
if (this.status === PEDDING) {
// Change the state to Rejected
this.status = REJECTED;
// Cache failure cause
this.reason = reason;
// If a value exists in the cache callback, execute
while (this.onRejectedCallback.length) {
// The callback is queued
this.onRejectedCallback.shift()(reason); }}};Copy the code
Write an example to execute the above program as follows:
const promise = new MyPromise((resolve, reject) = > {
setTimeout(() = > {
resolve("success");
reject("error");
});
});
promise.then(
(value) = > console.log(1."resolve"),
(reason) = > console.log(2."reject")); promise.then((value) = > console.log(3."resolve"),
(reason) = > console.log(4."reject"));/ / output:
// 1 resolve
// 2 resolve
Copy the code
Is the expected result, so far we have solved the asynchronous invocation problem, then method multiple invocation problem.
5. Then chain call
The then method chaining calls return a Promise. The previous THEN returns a Promise object, and the next THEN takes the previous resolve argument.
Here’s an example:
const promise = new MyPromise((resolve, reject) = > {
resolve();
});
promise
.then(() = > {
console.log(1."resolve");
return new MyPromise((resolve) = > {
resolve("success");
});
})
.then((value) = > console.log(2, value));
// output: 1 "resolve"
// test copy.html:99 Uncaught TypeError: Cannot read property 'then' of undefined
Copy the code
The error occurs because the then method does not return A value. In Promise A+, Promise’s then method is required to return A Promise object. As follows:
// ...
class MyPromise {
/ /...
/ / then method
then(onFulfilled, onRejected) {
const promise2 = new Promise((resolve, reject) = > {
// If the status is successful, the callback is successfully executed; if the status is failed, the callback is failed
if (this.status === FULFILLED) {
// Call the success callback to return the success value
const x = onFulfilled(this.value);
this.resolvePromise(x, resolve, reject);
} else if (this.status === REJECTED) {
// Call the failure callback to return the cause of failure
const x = onRejected(this.reason);
this.resolvePromise(x, resolve, reject);
} else {
// Cache the current success callback and queue it to success
this.onFulfilledCallback.push(onFulfilled);
// Cache the current failed callback and queue it to failure
this.onRejectedCallback.push(onRejected); }});return promise2;
};
resolvePromise = (x, resolve, reject) = > {
if (x instanceof MyPromise) {
x.then(resolve, reject);
} else{ resolve(x); }}; }Copy the code
Execute again:
const promise = new MyPromise((resolve, reject) = > {
resolve();
});
promise
.then(() = > {
console.log(1."resolve");
return new MyPromise((resolve) = > {
resolve("success");
});
})
.then((value) = > console.log(2, value));
/ / output
// 1 "resolve"
// 2 "success"
Copy the code
Now that we’ve implemented the chain call, what happens if the then method returns itself? Let’s see what happens if the Promise itself calls itself? Throws an error as follows:
Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
Copy the code
Take a look at the case where the code encapsulated above executes itself and returns itself as follows:
const promise = new MyPromise((resolve, reject) = > {
resolve();
});
const p1 = promise.then(() = > {
return p1;
});
// Output test copy.html:108 Uncaught (in promise) ReferenceError: Cannot access 'P1' before initialization
Copy the code
If then is returned, throw an error.
// ...
class MyPromise {
/ /...
/ / then method
then(onFulfilled, onRejected) {
const promise2 = new Promise((resolve, reject) = > {
// If the status is successful, the callback is successfully executed; if the status is failed, the callback is failed
if (this.status === FULFILLED) {
// Call the success callback to return the success value
const x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
} else if (this.status === REJECTED) {
// Call the failure callback to return the cause of failure
const x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} else {
// Cache the current success callback and queue it to success
this.onFulfilledCallback.push(onFulfilled);
// Cache the current failed callback and queue it to failure
this.onRejectedCallback.push(onRejected); }});return promise2;
};
resolvePromise = (promise2, x, resolve, reject) = > {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
}
if (x instanceof MyPromise) {
x.then(resolve, reject);
} else{ resolve(x); }}; }Copy the code
In execution let’s see
Uncaught (in promise) ReferenceError: Cannot access 'p1' before initialization
Copy the code
> < p class = “p0” > < P class = “p0” > < P class = “p0” > Based on eventLoop’s knowledge, you need to create an asynchronous task to wait for promisE2 to execute, using the queueMircotask method previously used.
then(onFulfilled, onRejected) {
const promise2 = new Promise((resolve, reject) = > {
// If the status is successful, the callback is successfully executed; if the status is failed, the callback is failed
if (this.status === FULFILLED) {
// === New part ===
queueMicrotask(() = > {
// Call the success callback to return the success value
const x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
});
} else if (this.status === REJECTED) {
// Call the failure callback to return the cause of failure
const x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} else {
// Cache the current success callback and queue it to success
this.onFulfilledCallback.push(onFulfilled);
// Cache the current failed callback and queue it to failure
this.onRejectedCallback.push(onRejected); }});return promise2;
};
Copy the code
The result of the run, and the type error we expect to throw, is as follows:
Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
Copy the code
6. Catch errors
Errors need to be caught at the executor stage of the code, and at the success and failure stages of the then method.
Catch actuator errors
constructor(executor) {
try {
// Execute the function immediately
executor(this.resolve, this.reject);
} catch (err) {
this.reject(err); }}Copy the code
Let’s take an example and see what happens
const promise = new MyPromise((resolve, reject) = > {
throw Error("ececutor error!");
resolve();
});
const p1 = promise.then(
(value) = > console.log("resolve", value),
(reason) = > console.log("reject", reason.message)
);
Reject ececutor error!
Copy the code
Error capture and code improvement in the THEN method
then = (onFulfilled, onRejected) = > {
const promise2 = new Promise((resolve, reject) = > {
const fulfilledMircotask = () = >
queueMicrotask(() = > {
try {
// Call the success callback to return the success value
const x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
} catch(error) { reject(error); }});const rejectedMircotask = () = >
queueMicrotask(() = > {
try {
// Call the failure callback to return the cause of failure
const x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch(error) { reject(error); }});// If the status is successful, the callback is successfully executed; if the status is failed, the callback is failed
if (this.status === FULFILLED) {
fulfilledMircotask();
} else if (this.status === REJECTED) {
rejectedMircotask();
} else {
// Cache the current success callback and queue it to success
this.onFulfilledCallback.push(fulfilledMircotask);
// Cache the current failed callback and queue it to failure
this.onRejectedCallback.push(rejectedMircotask); }});return promise2;
};
Copy the code
Let’s take an example and see what happens
const promise = new MyPromise((resolve, reject) = > {
resolve("success");
});
const p1 = promise
.then((value) = > {
console.log("resolve", value);
throw Error("then throw error!");
})
.then(
() = > console.log("resolve"),
(reason) = > {
console.log("reject", reason.message); });/ / output
// resolve success
// reject then throw error!
Copy the code
Error message in THEN was successfully printed.
7. Parameters in then are optional parameters
When implementing the THEN method, the onFulfilled and onRejected functions are passed in by default, which can also be optional.
then(onFulfilled, onRejected) {
// If it is not a function, it is converted to a function
const onFulfilledReal =
Object.prototype.toString.call(onFulfilled) === "[object Function]"
? onFulfilled
: (value) = > value;
const onRejectedReal =
Object.prototype.toString.call(onRejected) === "[object Function]"
? onRejected
: (reason) = > {
throw reason;
};
const promise2 = new Promise((resolve, reject) = > {
// ...
}
return promise2;
}
Copy the code
Here’s an example to test:
const promise = new MyPromise((resolve, reject) = > {
resolve("success");
});
const p1 = promise
.then()
.then()
.then(
(value) = > console.log("resolve", value),
(reason) = > console.log("reject", reason)
);
// Output resolve success
Copy the code
Expected results.
8. Improve the promiseResolve method
The code for promiseResolve in the evening according to the Promise A+ specification after 2.3.3
- X is a function or object, then = x.teng;
- If x. teng fails to execute, a reject(error) exception is thrown.
- If then is a function, call it with x as this and take two arguments: Then. Call (x, (y) => resolvePromise(promise, y, resolve, reject), (r) => reject(r));
- Otherwise, throw an error according to Promise A+
The code is as follows:
resolvePromise = (promise, x, resolve, reject) = > {
// If promise and X refer to the same object, i.e. call itself, then a type error is thrown
if (promise === x) {
return reject(
new TypeError("The promise and the return value are the same")); }if (typeof x === "object" || typeof x === "function") {
let then;
try {
// assign x. teng to then
then = x.then;
} catch (error) {
// If an error e is thrown when taking the value x. teng, reject a promise based on e
return reject(error);
}
// If then is a function
if (typeof then === "function") {
let called = false;
// call x as the function's scope this
// Pass two callback functions as arguments, resolvePromise and rejectPromise
try {
then.call(
x,
// If resolvePromise is called with the value y, run [[Resolve]](promise, y)
(y) = > {
// If both resolvePromise and rejectPromise are invoked,
// Or if the same argument is called more than once, the first call takes precedence and the rest are ignored
// Implement this by adding a variable called before it
if (called) return;
called = true;
resolvePromise(promise, y, resolve, reject);
},
// If rejectPromise is invoked with argument r, reject the promise with argument r
(r) = > {
if (called) return;
called = true; reject(r); }); }catch (error) {
// If calling the then method throws an exception e:
// If resolvePromise or rejectPromise has already been invoked, ignore it
if (called) return;
// Otherwise use e as the basis for rejecting the promisereject(error); }}else {
// If then is not a function, execute a promise with an x argumentresolve(x); }}else {
// If x is not an object or function, execute a promise with x as an argumentresolve(x); }};Copy the code
Resolve and reject static methods
// resolve static method
static resolve(value) {
// If it is an instance of the current object, return as-is
if (value instanceof MyPromise) {
return value;
}
// Re-create a Promise object
return new MyPromise((resolve) = > {
resolve(value);
});
}
// reject static methods
static reject(reason) {
// If it is an instance of the current object, return as-is
if (reason instanceof MyPromise) {
return reason;
}
// Re-create a Promise object
return new MyPromise((_, reject) = > {
reject(reason);
});
}
Copy the code
Test it out:
MyPromise.resolve("success").then((value) = > {
console.log("resolve", value); // resolve success
});
MyPromise.reject("error").then(
(value) = > {
console.log("resolve", value);
},
(reason) = > console.log("reject", reason) // reject error
);
/ / output
// resolve success
// reject error
Copy the code
10. Catch method implementation
The promise.prototype.catch () method is an alias for. Then (null, Rejection) or. Then (undefined, Rejection) that specifies the callback when an error occurs.
Code implementation:
/ / catch
catch(onRejected) {
this.then(undefined, onRejected);
}
Copy the code
Test it out:
const promise = new MyPromise((resolve, reject) = > {
reject("error");
});
const p1 = promise
.then((value) = > console.log("resolve", value))
.catch((err) = > {
console.log("catch", err);
});
// Print catch error
Copy the code
11 finally code implementation
The finally() method is used to specify actions that will be performed regardless of the final state of the Promise object.
Code implementation:
// finally executes regardless of success or failure
finally(fn) {
return this.then(
// Successful state execution
(value) = > {
return MyPromise.resolve(fn()).then(() = > {
return value;
});
},
// Failed to execute
(error) = > {
return MyPromise.resolve(fn()).then(() = > {
throwerror; }); }); }Copy the code
12. All method implementation
The All method returns a promise object that asks all the parameter states in the passed array to be fulfilled. The fulfilled state is fulfilled. If there is a Rejected state, the Fulfilled state is returned.
Analysis is as follows:
- Return a Promise: all = (promiseList) => new MyPromise(//…) ;
- All the states in the passed array will become the fulfilled state, then the fulfilled state will be returned, and the array passed in needs to be traversed. Mypromise.resolve (promiseList[I]).then((value) => {}, (reason) => {})
- The result is returned when the number of successes of promise.resolve () is equal to the length of the array passed in.
The code implementation is as follows:
static all(promiseList) {
return new MyPromise((resolve, reject) = > {
// Get the length of the array passed in
const len = promiseList.length;
// Initialize the result array
const result = [];
// Initialize the counter
let count = 0;
// Iterate over the passed method
promiseList.forEach((promise) = > {
MyPromise.resolve(promise).then(
(value) = > {
count++;
result[index] = value;
if (count === len) {
returnresolve(result); }},(reason) = > reject(reason)
);
});
});
}
Copy the code
13. Race method implementation
The RACE method, which returns a Promise object requiring that the first state to change for the passed parameter, whether it succeeds or fails, is the returned state.
Analysis:
- Return a promise, race = (promiseList) => new MyPromise(() => {});
- To iterate over the incoming promiseelist and get each of the incoming promiseItems, use the for loop because any return breaks the loop.
- Success or failure, if the first state changes, return, MyPromise.resolve(promiseList[i]).then((value) => { return reslove(value) }, (reason) => { return reject(reason); })
Code implementation:
// race static method that returns whoever completes first
static race(promiseList) {
return new MyPromise((resolve, reject) = > {
// Get the length of the array passed in
const len = promiseList.length;
if (len === 0) {
resolve();
} else {
for (let i = 0; i < len; i++) {
MyPromise.resolve(promiseList[i]).then(
(value) = > {
return resolve(value);
},
(reason) = > {
returnreject(reason); }); }}}); }Copy the code
14. AllSettled method implementation
The promise.allSettled () method returns a Promise after all the given promises have fulfilled or rejected, with an array of objects each representing the corresponding Promise result.
Once each promise in the set of promises specified has been completed, unpromised promises will be completed asynchronously, whether successfully made or rejected. At that point, the returned promise processor will pass in as input an array of promises containing the results of each promise in the original Promises set.
For each result object, there is a status string. If its value is fulfilled, there is a value on the result object. If the value is rejected, there is a reason. Value (or Reason) reflects the value of each promise resolution (or rejection).
Analysis:
- Return a promise object, allSettled = (promiseList) => new MyPromise(() => {});
- Iterate over the promiseList and record the sum of the number of failures or successes.
- Returns when the sum of all numbers equals the length of the array.
Code implementation:
// allSettled static implementation
static allSettled(promiseList) {
return new MyPromise((reslove, reject) = > {
// Cache array length
const len = promiseList.length;
// Initialize the result array
const result = [];
// Initialize the counter
let count = 0;
// If an empty array is passed in, an empty array is returned
if (len === 0) {
resolve(result);
} else {
// iterate over the incoming array
for (let i = 0; i < len; i++) {
MyPromise.resolve(promiseList[i]).then(
(value) = > {
// Success increments the counter by 1
count++;
// Save the successful state and set the state to depressing
result[i] = {
status: "fulfilled",
value,
};
// If the length of the array passed is equal to that of the technician, resolve is returned
if (len === count) {
returnresolve(result); }},(reason) = > {
// Fail, counter increment 1
count++;
// Store the result of the failed state and set the return status to Rejected
result[i] = {
status: "rejected",
reason,
};
// If the length of the array passed is equal to that of the technician, resolve is returned
if (len === count) {
returnresolve(result); }}); }}}); }Copy the code
At this point, a custom version of the Promise is complete. Use Promises – aplus-Tests to install and test it
I Promise A+ test
Promises that are implemented by hand need to comply with the Promise A+ specification, and promises-aplus-tests are required to fully comply with the Promise A+ specification.
1. Install the promises – aplus – tests
Yarn add promises- aplus-tests-d or NPM install promises- aplus-tests-dCopy the code
2. Add the test code Deferred
MyPromise.deferred = function () {
var result = {};
result.promise = new MyPromise(function (resolve, reject) {
result.resolve = resolve;
result.reject = reject;
});
return result;
};
Copy the code
3. Configure the package.json file
{
"name": "my-promise"."version": "1.0.0"."description": ""."main": "my-promise.js".// Import file
"scripts": {
"test": "promises-aplus-tests my-promise" // Configure commands
},
"author": ""."license": "ISC"."devDependencies": {
"promises-aplus-tests": "^ 2.1.2"}}Copy the code
4. Start the test
yarn test & npm run test
Copy the code
Effect:
conclusion
From the basic use of Promise, Promise A+, handwritten implementation of Promise, handwritten code testing and other aspects of the analysis of the underlying implementation of Promise. Promise is a solution to the asynchronous scheme. However, if the link is long, the code will also be difficult to maintain. For this situation, es6 proposes an asynchronous solution of Generator function and Async await. For more information, please check out the following articles.
It is not easy to write a long article, and the best encouragement is to support it.
Handwritten Promise has been put into warehouse MyPromise.
reference
- Ruan Yifeng teacher ES6 Promise
- Promise A+
- Classic INTERVIEW Questions: The most detailed handwritten Promise tutorial ever