current position:Home>[hand tear series] hand tear promise -- this article takes you to realize promise perfectly according to promise a + specification!
[hand tear series] hand tear promise -- this article takes you to realize promise perfectly according to promise a + specification!
2022-04-29 13:13:29【Straw hat plastic】
This article is based on Promise A+
standard , Take you step by step from shallow to deep Promise
, After reading patiently, you can definitely understand and realize !
The source code is in mine github in , You can take it yourself if you need it :github.com/Plasticine-…
1. Basic function realization
First of all, make it clear Promise
It's a constructor , Able to receive a executor
Parameters , This parameter is a function , It takes two parameters executor(resolve, reject)
So in our MyPromise
Should this be executed in the constructor of executor
function , And passed in resolve
and reject
Parameters , These two parameters are functions , And in our MyPromise
Achieve in
Because of each Promise
Example of resolve
and reject
Functions should be independent of each other , That is, they all belong to the instance itself , Therefore, it is not suitable to define in the constructor prototype
On , stay ES6 class
From the perspective of , That is to say... Should not be resolve
and reject
A function is defined as a method of a class , Instead, you should define... Inside the constructor , This ensures that they will create multiple in memory when each instance instantiates and calls the constructor , If defined as a method , Will hang to the constructor prototype
On
According to the above characteristics , We can write a whole framework first
class MyPromise {
constructor(executor) {
const resolve = () => {}
const reject = () => {}
executor(resolve, reject)
}
}
Copy code
We all know ,Promise
It's stateful , according to Promise A+
standard , There are three states
A promise must be in one of three states: pending, fulfilled, or rejected.
So now we'll give us MyPromise
Define three state constants
/** * @description MyPromise The state of */
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
Copy code
Every Promise
The instances are all... At the beginning pending
state , Unless an external call resolve
perhaps reject
Function will change promise Status of the instance , Therefore, the constructor should first initialize the state to pending
constructor(executor) {
this.status = PENDING
}
Copy code
secondly , according to Promise A+
standard , Every Promise
Instances can call then
Method , from onFulfilled
Call back to deal with resolved
State of value
, And by onRejected
Call back to deal with rejected
State of reason
, Give us an example to maintain value
and reason
So that it can be used by the corresponding callback later
constructor(executor) {
this.value = undefined
this.reason = undefined
}
Copy code
This is initialized to undefined
Because if the external calls resolve
perhaps reject
If there is no parameter passed in the function , stay js When no argument is passed in, accessing the formal parameter is undefined
, therefore onFulfilled/onRejected
In callback value/reason
It should also be undefined
, This is for and js The function characteristics of are unified
function foo(text) {
console.log(text)
}
foo() // => undefined
Copy code
And then there's the resolve/reject
Process the change of state and set the corresponding value/reason
了
constructor(executor) {
const resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
}
}
const reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
}
}
executor(resolve, reject)
}
Copy code
Here are a few points worth noting :
- The change of state can only be from
pending
Change tofulfilled/rejected
, It can't be other changes , Therefore, before changing the state, you must judge whether the current state ispending
value/reason
The modification of must take effect after the status changes , Therefore, it should also be put in the judgment statement- The most important point !!!
resolve/reject
Functions can only be used with Arrow function To define , You must not use ordinary functions to define , This involves arrow functions and ordinary functionsthis
Pointed question- Because you need to
resolve/reject
Used inthis
, And make surethis
It's pointingMyPromise
Example of - Of a normal function this The point is determined when calling externally , because
this
The default binding for 、 Implicit binding 、 Explicit binding and other features lead tothis
The direction of is not clear , There is no guarantee of pointing to the instance itself - Not in arrow function
this
, according tojs
Characteristics of lexical scope , It will use the... In the parent function definition field when the function is definedthis
, That is to sayconstructor
Functionthis
- Because you need to
- The arrow function uses const Keyword declares a reference variable to point to it , Therefore, it is necessary to implement executor Previously defined , Otherwise, it will be unable to find... Due to temporary dead zone resolve, reject function
The next step is then
Method is implemented ,then
Method accepts two parameters ,onFulfilled
and onRejected
, Are two callback functions , according to Promise A+
standard , call then
Methods in fulfilled
State onFullfilled
Callback , stay rejected
State onRejected
Callback , According to this characteristic , You can write the following code :
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value)
}
if (this.status === REJECTED) {
onRejected(this.reason)
}
}
Copy code
Now we have got a that can realize the basic functions Promise
了 , The current complete code is as follows :
/** * @description MyPromise The state of */
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
class MyPromise {
constructor(executor) {
this.status = PENDING // The initial state is PENDING
this.value = undefined // resolve The value of going out
this.reason = undefined // reject The reason for going out
const resolve = (value) => {
// Only in PENDING State can be switched to FULFILLED state
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
}
}
const reject = (reason) => {
// Only in PENDING State can be switched to REJECTED state
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
}
}
executor(resolve, reject)
}
then(onFulfilled, onRejected) {
// Determine which callback to execute according to the status
if (this.status === FULFILLED) {
onFulfilled(this.value)
}
if (this.status === REJECTED) {
onRejected(this.reason)
}
}
}
Copy code
Test it
function foo() {
return new MyPromise((resolve, reject) => {
resolve('resolved value')
})
}
foo().then(
(value) => {
console.log(value)
},
(reason) => {
console.log(reason)
}
)
// => resolved value
Copy code
function foo() {
return new MyPromise((resolve, reject) => {
reject('rejected reason')
})
}
foo().then(
(value) => {
console.log(value)
},
(reason) => {
console.log(reason)
}
)
// => rejected reason
Copy code
2. exception handling
At present, our MyPromise
What happens when you encounter an exception ? Can be like Promise
Be like that onRejected
Catch it ?
Obviously not , Because we didn't handle any exceptions . Then think about where exception handling should be added so that exceptions can be caught and passed to onRejected
Callback to deal with ?
First of all, we should clarify where the exception comes from , The exception must be when calling the constructor from outside executor
Function , Because the main business logic is executor
Writing in the
So if you call executor
An exception occurred in the process of , We should be in our MyPromise
To capture it , And because I want to be onRejected
Handle , Therefore, you need to change the status to rejected
, And will reason
Set to the exception caught , This part of the logic is not reject
What functions do ? So we can reuse , call reject
that will do
constructor(executor) {
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
Copy code
Now the exception can be onRejected
Processed
function foo() {
return new MyPromise((resolve, reject) => {
throw new Error('something wrong...')
})
}
foo().then(
(value) => {
console.log(`resolved: ${value}`)
},
(reason) => {
console.log(`rejected: ${reason}`)
}
)
// => rejected: Error: something wrong...
Copy code
3. Use release - Subscription mode solves the problems of asynchrony and multiple calls
At present, our MyPromise
What happens if you encounter asynchronous execution in ? For example, in executor
in , Delay two seconds to execute resolve
function , So it can be then
Methodical onFulfilled
Do you deal with it ?
function foo() {
return new MyPromise((resolve, reject) => {
setTimeout(() => resolve('resolved value'), 2000)
})
}
foo().then(
(value) => {
console.log(`resolved: ${value}`)
},
(reason) => {
console.log(`rejected: ${reason}`)
}
)
// =>
Copy code
You can see that there is no output , because executor
The execution of is synchronous , encounter setTimeout
The execution callback will be placed in the macro task queue of the event loop , Then immediately execute then
The method , At this time, because there is no call resolve
, So the status is still the same pending
, that then
Naturally, it can't be handled
Wait until two seconds later, the callback in the timer executes , Changed the State , But this time js The code in the thread has already been executed , That is to say then
It's long over , So no other code can handle resolve
After resolved
state
Maybe we'll come up with the following solution
function foo() {
return new MyPromise((resolve, reject) => {
// resolve('resolved value')
// reject('rejected reason')
// throw new Error('something wrong...')
setTimeout(() => resolve('resolved value'), 2000)
})
}
const myPromise = foo()
// 2.5 We'll do it in seconds then Method treatment resolved state
setTimeout(() => {
myPromise.then(
(value) => {
console.log(`resolved: ${value}`)
},
(reason) => {
console.log(`rejected: ${reason}`)
}
)
}, 2500)
// => resolved: resolved value
Copy code
Be careful : The two timers start counting almost at the same time , So finally print out **resolved: resolved value**
The time taken is not **2 + 2.5 = 4.5 second **
, It is **2.5 second **
This scheme seems to be able to solve the problem of asynchronous call , But has it really solved ?
Just imagine , If we don't use a timer now , It is a axios/fetch
Of ajax
What about the request ? We only call after the request has a result resolve
function , You can still die like this 2.5 Second timer to call then
Methods? ?
Obviously not , Because I don't know this ajax How long will the request take , If you are lucky , It's in 2.5 The result is returned in seconds , that then
It can still handle the results normally , But if it goes beyond 2.5 Second is not enough , Do you want to set a super long timer , When it's due, do it again then
Methods? ? It's obviously outrageous
It is necessary to consider improving our MyPromise
了 , We need to use ** Release - subscribe **
This design pattern to solve this problem
Think about it first , The reason why asynchronous calls cannot be triggered onResolved
Callback , No, it's just because you execute... In synchronous code then
Method time , Asynchronous code hasn't called yet resolve
Well , So the whole MyPromise
The status of the instance is still pending
state
In that case , So we can do that then
Method pending
Status , Maintain a container , Used exclusively for storage onResolved
Callback ( Because it may be called multiple times then
Method , Every then
Each method has its own onResolved
Callback ), Then call... In asynchronous code resolve
When , Take it out of this container in turn onResolved
Just call back and execute !
in fact :
then
In dealing withpending
, takeonResolved
andonRejected
The process of adding to the corresponding container is “ subscribe ” The process of , subscribefulfilled
andrejected
eventresolve
Put in the containeronResolved
The callback is taken out in turn, and the execution process is “ Release ” The process of , Releasefulfilled
newsreject
It's the same thing , It's just that it's from storageonRejected
It's just to get the callback from the callback container , Also a “ Release ” The process of , Released isrejected
news
Just look at the code !
then(onFulfilled, onRejected) {
// Determine which callback to execute according to the status
if (this.status === FULFILLED) {
onFulfilled(this.value)
}
if (this.status === REJECTED) {
onRejected(this.reason)
}
if (this.status === PENDING) {
// pending In this state, you need “ subscribe ” fulfilled and rejected event
this.onFulfilledCallbackList.push(() => onFulfilled(this.value))
this.onRejectedCallbackList.push(() => onRejected(this.reason))
}
}
const resolve = (value) => {
// Only in PENDING State can be switched to FULFILLED state
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
// Trigger resolve Behavior , Start “ Release ” resolved event
this.onFulfilledCallbackList.forEach((fn) => fn())
}
}
const reject = (reason) => {
// Only in PENDING State can be switched to REJECTED state
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
// Trigger reject Behavior , Start “ Release ” rejected event
this.onRejectedCallbackList.forEach((fn) => fn())
}
}
Copy code
Now you can handle asynchronous code normally
function foo() {
return new MyPromise((resolve, reject) => {
setTimeout(() => resolve('resolved value'), 2000)
})
}
foo().then(
(value) => {
console.log(`resolved: ${value}`)
},
(reason) => {
console.log(`rejected: ${reason}`)
}
)
// => resolved: resolved value
Copy code
And this design has another advantage , For multiple calls then
Method time , We must want to call then
Methods to execute callbacks among them , Because the two containers we maintain are actually a queue , Callbacks are first in, first out , This ensures the order and accuracy of callback execution then
Methods are called in the same order
function foo() {
return new MyPromise((resolve, reject) => {
setTimeout(() => resolve('resolved value'), 2000)
})
}
const myPromise = foo()
myPromise.then(
(value) => {
console.log(`resolved1: ${value}`)
},
(reason) => {
console.log(`rejected1: ${reason}`)
}
)
myPromise.then(
(value) => {
console.log(`resolved2: ${value}`)
},
(reason) => {
console.log(`rejected2: ${reason}`)
}
)
myPromise.then(
(value) => {
console.log(`resolved3: ${value}`)
},
(reason) => {
console.log(`rejected3: ${reason}`)
}
)
/** => resolved1: resolved value resolved2: resolved value resolved3: resolved value */
Copy code
4. Solve the problem of chain call
Native Promise
It supports chain call
const promise = new Promise((resolve, reject) => {
resolve('plasticine')
})
promise
.then((value) => {
console.log(`then1 -- ${value}`)
return value
})
.then((value) => {
console.log(`then2 -- ${value}`)
return new Promise((resolve, reject) => resolve(value))
})
.then((value) => {
console.log(`then3 -- ${value}`)
return Promise.resolve(value)
})
.then((value) => {
console.log(`then4 -- ${value}`)
})
/** => then1 -- plasticine then2 -- plasticine then3 -- plasticine then4 -- plasticine */
Copy code
At present, our MyPromise
Such a function is not supported yet , Why is it possible to chain call ? That must be because then
Method returns Promise
Examples , in fact Promise A+
The specification clearly stipulates :
then must return a promise.
promise2 = promise1.then(onFulfilled, onRejected);
- If either
onFulfilled
oronRejected
returns a valuex
, run the Promise Resolution Procedure[[Resolve]](promise2, x)
.- If either
onFulfilled
oronRejected
throws an exceptione
, promise2 must berejected
with e as thereason
.- If onFulfilled is not a function and promise1 is fulfilled, promise2 must be fulfilled with the same value as promise1.
- If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason as promise1.
4.1 then Back in Promise Implement chain call
So according to this specification , We can modify it then
Method implementation , Wrap a layer MyPromise
object promise2
, And return this object to
then(onFulfilled, onRejected) {
const promise2 = new MyPromise((resolve, reject) => {
// Determine which callback to execute according to the status
if (this.status === FULFILLED) {
onFulfilled(this.value)
}
if (this.status === REJECTED) {
onRejected(this.reason)
}
if (this.status === PENDING) {
// pending In this state, you need “ subscribe ” fulfilled and rejected event
this.onFulfilledCallbackList.push(() => onFulfilled(this.value))
this.onRejectedCallbackList.push(() => onRejected(this.reason))
}
})
return promise2
}
Copy code
4.2 Capture then
The return values of the two callbacks in x
The first article of the specification also mentions ,onFulFilled
and onRejected
The callback will return a x
, Then add
then(onFulfilled, onRejected) {
const promise2 = new MyPromise((resolve, reject) => {
// Determine which callback to execute according to the status
if (this.status === FULFILLED) {
let x = onFulfilled(this.value)
}
if (this.status === REJECTED) {
let x = onRejected(this.reason)
}
if (this.status === PENDING) {
// pending In this state, you need “ subscribe ” fulfilled and rejected event
this.onFulfilledCallbackList.push(() => {
let x = onFulfilled(this.value)
})
this.onRejectedCallbackList.push(() => {
let x = onRejected(this.reason)
})
}
})
return promise2
}
Copy code
And then this x
Yes, it will be resolve
Out of the , In this way, it can be used by the next then
Method received
Then you have to think about x
What will it be , from TS
From the perspective of ,x
yes any
Type of , It may be a basic data type or a reference data type , It doesn't matter , The trouble is x
It could be MyPromise
object , So this needs to be studied
Because if you just put MyPromise
The object is to resolve
get out , next then
I got a MyPromise
object , It can't get what it really wants value
, So for x
yes MyPromise
About the object , It should carry out the resolve
function
4.3 Handle then Exception thrown during callback execution
The second article of the specification refers to onFulfilled
and onRejected
The abnormal , Will act as promise2
Of reason
By reject
get out , Then we will try/catch
Capture it , In case of exception, call promise2
Of reject
that will do
then(onFulfilled, onRejected) {
const promise2 = new MyPromise((resolve, reject) => {
// Determine which callback to execute according to the status
if (this.status === FULFILLED) {
try {
let x = onFulfilled(this.value)
} catch (e) {
reject(e)
}
}
if (this.status === REJECTED) {
try {
let x = onRejected(this.reason)
} catch (e) {
reject(e)
}
}
if (this.status === PENDING) {
// pending In this state, you need “ subscribe ” fulfilled and rejected event
this.onFulfilledCallbackList.push(() => {
try {
let x = onFulfilled(this.value)
} catch (e) {
reject(e)
}
})
this.onRejectedCallbackList.push(() => {
try {
let x = onRejected(this.reason)
} catch (e) {
reject(e)
}
})
}
})
return promise2
}
Copy code
4.4 resolvePromise Handle x
Next, we should deal with x
了 , The first of the norms is [[Resolve]](promise2, x)
In fact, it is to call [[Resolve]]
Function to handle promise2
Instance and returned x
alike , The specification States , This [[Resolve]]
The name of the function in the specification is resolvePromise
, Then we should get promise2
and x
Then call such a function
const promise2 = new MyPromise((resolve, reject) => {
// Determine which callback to execute according to the status
if (this.status === FULFILLED) {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x)
} catch (e) {
reject(e)
}
}
...
}
Copy code
4.4.1 Asynchronous execution resolvePromise Ensure that the parameters can be obtained
But there's a problem ,promise2
Can you get it ?promise2
Is in MyPromise
After the constructor of is executed, you will get , Now we need to use... In advance inside the constructor promise2
example , There's obviously a problem
secondly , Then we need to call promise2
Of resolve/reject
To deal with it x
Of , But they are defined in MyPromise
Inside the constructor , therefore resolvePromise
Can't access , So we also need to put resolve/reject
And to resolvePromise
function
const promise2 = new MyPromise((resolve, reject) => {
// Determine which callback to execute according to the status
if (this.status === FULFILLED) {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}
...
}
Copy code
Okay , Then the next step is to solve how to obtain promise2
The problem. , The question is Promise A+
The specification also mentions
onFulfilled or onRejected must not be called until the execution context stack contains only platform code.
Here “platform code” means engine, environment, and promise implementation code. In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack. This can be implemented with either a “macro-task” mechanism such as setTimeout or setImmediate, or with a “micro-task” mechanism such as MutationObserver or process.nextTick. Since the promise implementation is considered platform code, it may itself contain a task-scheduling queue or “trampoline” in which the handlers are called.
You can see , You can use macro tasks or micro tasks to achieve , thus , The entire constructor will be executed in js When the thread executes, execute , The macro task will be added to the macro task queue , Then it's time to execute macro tasks ,promise2
The instance has been created , therefore resolvePromise
You can use it normally
Of course , You can also use micro tasks , Native Promise
It is implemented in the form of micro tasks , For the sake of simplicity , Direct use setTimeout
Realization , It is implemented in the form of macro tasks
then(onFulfilled, onRejected) {
const promise2 = new MyPromise((resolve, reject) => {
// Determine which callback to execute according to the status
if (this.status === FULFILLED) {
// Execute as a macro task resolvePromise To ensure that you get promise2 example
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x)
} catch (e) {
reject(e)
}
}, 0)
}
})
}
Copy code
here **setTimeout**
The default delay is 0, It's OK not to fill in , I filled it out just to emphasize the use of **setTimeout**
Just to execute in the form of macro tasks **resolvePromise**
, Let him execute in the next round of the event cycle , Not immediately , And pay attention to , Although it is **0ms**
Delay of , But there will still be at least **4ms**
Delay of , This point **MDN**
There's an explanation on , Check for yourself
4.4.2 Realization resolvePromise
The next step is to realize resolvePromise
了 ,resolvePromise
The implementation of also needs to follow Promise A+
To achieve
4.4.2.1 An exception is thrown when referring to a loop
First of all, according to the 2.3.1
, If promise
and x
It refers to the same object , Should be reject
One TypeError
As reason
Start with the original Promise
Show me , Deepen the understanding
const promise1 = new Promise((resolve, reject) => {
resolve('plasticine')
})
const promise2 = promise1.then(
(value) => {
return promise2
},
(reason) => {
return reason
}
)
promise2.then(
(value) => {
console.log(value)
},
(reason) => {
console.log(reason)
}
)
// => TypeError: Chaining cycle detected for promise #<Promise>
Copy code
So we can do that resolvePromise
To judge ,promise === x
when reject
One TypeError
get out
/** * * @param {MyPromise} promise MyPromise example * @param {any} x Previous MyPromise resolve The value of * @param {Function} resolve promise In the object constructor resolve * @param {Function} reject promise In the object constructor reject */
function resolvePromise(promise, x, resolve, reject) {
if (promise === x) {
return reject(
new TypeError('Chaining cycle detected for promise #<MyPromise>')
)
}
}
Copy code
4.4.2.2 Judge x Whether it is MyPromise example
Let's take a look 2.3.3
(2.3.2
You can skip , It's actually telling us to keep promise
It's just a state of )
2.3.3
We need to judge x
Is it a object/function
then 2.3.3.1
And make then = x.then
, And then I will judge then
Is it a function
The purpose of this is to judge x
Is it a MyPromise
object , Yes then
The method regards it as MyPromise
object
take **then = x.then**
One thing to note in this process ,**x.then**
May be **Object.defineProperty**
Set up **getter**
hijacked , If **getter**
Exception thrown in , You need to treat this exception as **reason**
Take it **reject**
get out , This is what **2.3.3.2**
Stipulated in
Object.defineProperty(x, 'then', {
get() {
throw new Error('something wrong...')
}
})
Copy code
So we need to use try/catch
Wrap it let then = x.then
, And then according to 2.3.3.3.1
and 2.3.3.3.2
, When x
It's a MyPromise
When the object , We need to call it then
Method , And you need to explicitly bind this
by x
, Pass in two callbacks at the same time :resolvePromise
and rejectPromise
this **resolvePromise**
Not the whole outside **resolvePromise(promise, x, resolve, reject)**
, It's another definition , Here we directly pass in the form of arrow function , Are two anonymous functions
And according to 2.3.3.3.1
,resolvePromise
The callback receives a parameter y
, Then recursively call resolvePromise(promise, y, resolve, reject)
, Because if x
It's a MyPromise
Words , its then
It is still possible to return MyPromise
object , Therefore, recursive processing is required
According to the above points , What we have now resolvePromise(promise, x, resolve, reject)
as follows :
/** * according to x Make different treatment for different types of * x For the wrong MyPromise When the object -- direct resolve * x by MyPromise When the object -- Call its resolve * * The remaining details will be commented in the code * * @param {MyPromise} promise MyPromise example * @param {any} x Previous MyPromise resolve The value of * @param {Function} resolve promise In the object constructor resolve * @param {Function} reject promise In the object constructor reject */
function resolvePromise(promise, x, resolve, reject) {
// according to Promise A+ standard 2.3.1: promise and x When pointing to the same reference, you should reject One TypeError
if (promise === x) {
return reject(
new TypeError('Chaining cycle detected for promise #<MyPromise>')
)
}
// 2.3.3: x yes object or function The case when
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
// typeof null === 'object', Therefore, we need to make an additional judgment to exclude x yes null The situation of
try {
// x.then May be Object.defineProperty Set up getter hijacked , And throw an exception , So we have to try/catch Capture
let then = x.then
if (typeof then === 'function') {
// x Yes then When the method is used , Think of it as MyPromise object
// 2.3.3.3 perform x.then, And explicitly bind this Point to , And there are two callbacks resolvePromise and rejectPromise
then.call(
x,
// resolvePromise
(y) => {
// Need to call recursively MyPromise in resolve The value of going out , That's what we have here y
resolvePromise(promise, y, resolve, reject)
},
// rejectPromise
(r) => {
// about reject, Directly reason to reject Just go out
reject(r)
}
)
} else {
// x No, then Method -- No special treatment is required , direct resolve
resolve(x)
}
} catch (e) {
reject(e)
}
} else {
// Basic data type -- direct resolve
resolve(x)
}
}
Copy code
4.4.2.3 Prevent multiple executions then Callback in
according to 2.3.3.3.3
, When both callbacks are executed , We should only carry out the first , What does that mean ? Use native Promise
Show me
const promise1 = new Promise((resolve, reject) => {
resolve('plasticine')
})
const promise2 = promise1.then(
(value) => {
return new Promise((resolve, reject) => {
resolve(value)
reject(new Error('something wrong...'))
})
},
(reason) => {
return reason
}
)
promise2.then(
(value) => {
console.log(value)
},
(reason) => {
console.log(reason)
}
)
// => plasticine
Copy code
That is to say, if you are then
Of onFulfilled
Calling back , Return to new Promise
When an instance , Both... Are called in the constructor resolve
Call again reject
Words , Only the first called... Will be executed , The rest will be ignored , In this case reject
It's ignored
So our MyPromise
How to realize this function ? Can be marked with a Boolean value , Express x.then
Whether any of the two callbacks in have been called , Some will not execute them again
function resolvePromise(promise, x, resolve, reject) {
// according to Promise A+ standard 2.3.1: promise and x When pointing to the same reference, you should reject One TypeError
if (promise === x) {
return reject(
new TypeError('Chaining cycle detected for promise #<MyPromise>')
)
}
// 2.3.3: x yes object or function The case when
let isCalled = false // When x yes MyPromise When an instance , Mark x.then Whether any of the callbacks in have been called
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
// typeof null === 'object', Therefore, we need to make an additional judgment to exclude x yes null The situation of
try {
// x.then May be Object.defineProperty Set up getter hijacked , And throw an exception , So we have to try/catch Capture
let then = x.then
if (typeof then === 'function') {
// x Yes then When the method is used , Think of it as MyPromise object
// 2.3.3.3 perform x.then, And explicitly bind this Point to , And there are two callbacks resolvePromise and rejectPromise
then.call(
x,
// resolvePromise
(y) => {
// Need to call recursively MyPromise in resolve The value of going out , That's what we have here y
if (isCalled) return // Any callback has been executed , Ignore the execution of the current callback
isCalled = true // Execute callback for the first time , Set the flag variable to true, Prevent repeated calls or calls rejectPromise
resolvePromise(promise, y, resolve, reject)
},
// rejectPromise
(r) => {
// about reject, Directly reason to reject Just go out
if (isCalled) return // Any callback has been executed , Ignore the execution of the current callback
isCalled = true // Execute callback for the first time , Set the flag variable to true, Prevent repeated calls or calls resolvePromise
reject(r)
}
)
} else {
// x No, then Method -- No special treatment is required , direct resolve
resolve(x)
}
} catch (e) {
// If something goes wrong , Should not be able to call resolve, Therefore, we should also add isCalled Judge , Prevent calling resolve
if (isCalled) return
isCalled = true
reject(e)
}
} else {
// Basic data type -- direct resolve
resolve(x)
}
}
Copy code
4.5 A functional test
There is nothing in the later specifications , Now you can test whether the function is normal
const promise1 = new MyPromise((resolve, reject) => {
resolve('plasticine')
})
const promise2 = promise1.then(
(value) => {
return new MyPromise((resolve, reject) => {
resolve(value)
reject(new Error('something wrong...'))
})
},
(reason) => {
return reason
}
)
promise2.then(
(value) => {
console.log(value)
},
(reason) => {
console.log(reason)
}
)
// => plasticine
Copy code
const promise2 = promise1.then(
(value) => {
return new MyPromise((resolve, reject) => {
// Asynchronous call resolve
setTimeout(() => {
resolve(value)
}, 2000)
})
},
(reason) => {
return reason
}
)
promise2.then(
(value) => {
console.log(value)
},
(reason) => {
console.log(reason)
}
)
// => plasticine
Copy code
You can see , And native Promise
Same effect , stay onFulfilled
One was returned in MyPromise
Can still be in the back then
Get in the resolve
Out of the value
And both call resolve
Call again reject
,reject
Neglected , Also with native Promise
Act in unison , Call asynchronously at the same time resolve
It can also work normally
5. then Callback is an optional parameter
Promise A+
The specification defines then
Two callbacks for onFulfilled
and onRejected
It's optional , That means you can directly .then()
call
Then when calling like this, we should resolve
Of value
Pass through , Until there is then
The callback onFulfilled
To deal with , So we can give onFulfilled
and onRejected
The default value is
then(onFulfilled, onRejected) {
// onFulfilled and onRejected Is an optional parameter , If it is not transmitted, the default value should be set
onFulfilled =
typeof onFulfilled === 'function' ? onFulfilled : (value) => value
onRejected =
typeof onRejected === 'function'
? onRejected
: (reason) => {
throw reason
}
// ...
}
Copy code
Test it then
Whether the callback can be penetrated when it is not passed in
new MyPromise((resolve, reject) => {
resolve('plasticine')
})
.then()
.then()
.then()
.then()
.then((value) => {
console.log(value)
})
// => plasticine
Copy code
It can !
6. Realization catch
Is it really finished ? Native Promise
Object has one more catch
Methods? !
in fact ,catch
The thing to do is then
What the second callback does , Are used to deal with reject
Coming out reason
Of ,Promise A+
There is no provision in catch
This method , In fact, you can put catch
Method as then
The syntax of the second callback , That is, only the second callback is passed in :this.then(null, () => {})
So let's reuse it directly then
Can be realized catch
了
catch(onRejected) {
return this.then(null, onRejected)
}
Copy code
7. Complete code
thus , Whole Promise
Even if the function of is fully realized , The complete code is as follows
/** * @description MyPromise The state of */
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
/** * according to x Make different treatment for different types of * x For the wrong MyPromise When the object -- direct resolve * x by MyPromise When the object -- Call its resolve * * The remaining details will be commented in the code * * @param {MyPromise} promise MyPromise example * @param {any} x Previous MyPromise resolve The value of * @param {Function} resolve promise In the object constructor resolve * @param {Function} reject promise In the object constructor reject */
function resolvePromise(promise, x, resolve, reject) {
// according to Promise A+ standard 2.3.1: promise and x When pointing to the same reference, you should reject One TypeError
if (promise === x) {
return reject(
new TypeError('Chaining cycle detected for promise #<MyPromise>')
)
}
// 2.3.3: x yes object or function The case when
let isCalled = false // When x yes MyPromise When an instance , Mark x.then Whether any of the callbacks in have been called
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
// typeof null === 'object', Therefore, we need to make an additional judgment to exclude x yes null The situation of
try {
// x.then May be Object.defineProperty Set up getter hijacked , And throw an exception , So we have to try/catch Capture
let then = x.then
if (typeof then === 'function') {
// x Yes then When the method is used , Think of it as MyPromise object
// 2.3.3.3 perform x.then, And explicitly bind this Point to , And there are two callbacks resolvePromise and rejectPromise
then.call(
x,
// resolvePromise
(y) => {
// Need to call recursively MyPromise in resolve The value of going out , That's what we have here y
if (isCalled) return // Any callback has been executed , Ignore the execution of the current callback
isCalled = true // Execute callback for the first time , Set the flag variable to true, Prevent repeated calls or calls rejectPromise
resolvePromise(promise, y, resolve, reject)
},
// rejectPromise
(r) => {
// about reject, Directly reason to reject Just go out
if (isCalled) return // Any callback has been executed , Ignore the execution of the current callback
isCalled = true // Execute callback for the first time , Set the flag variable to true, Prevent repeated calls or calls resolvePromise
reject(r)
}
)
} else {
// x No, then Method -- No special treatment is required , direct resolve
resolve(x)
}
} catch (e) {
// If something goes wrong , Should not be able to call resolve, Therefore, we should also add isCalled Judge , Prevent calling resolve
if (isCalled) return
isCalled = true
reject(e)
}
} else {
// Basic data type -- direct resolve
resolve(x)
}
}
class MyPromise {
constructor(executor) {
this.status = PENDING // The initial state is PENDING
this.value = undefined // resolve The value of going out
this.reason = undefined // reject The reason for going out
// Maintain two list containers Store the corresponding callback
this.onFulfilledCallbackList = []
this.onRejectedCallbackList = []
/** * resolve, reject It's two functions -- Every MyPromise In the instance resolve The method is your own * If you will resolve, reject If defined outside the constructor , Method will be in the constructor prototype On * So you should define... Inside the constructor resolve, reject function * * And be sure to use the arrow function to define It can't be an ordinary function * Of a normal function this The point is determined when calling externally , There is no guarantee of pointing to the instance itself * And there's nothing in the arrow function this, The parent function of function definition will be used constructor Medium this * In other words, using the arrow function can ensure this Point to the instance itself * * The arrow function uses const Keyword declares a reference variable to point to it , Therefore, it is necessary to implement executor Previously defined * Otherwise, it will be unable to find... Due to temporary dead zone resolve, reject function */
const resolve = (value) => {
// Only in PENDING State can be switched to FULFILLED state
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
// Trigger resolve Behavior , Start “ Release ” resolved event
this.onFulfilledCallbackList.forEach((fn) => fn())
}
}
const reject = (reason) => {
// Only in PENDING State can be switched to REJECTED state
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
// Trigger reject Behavior , Start “ Release ” rejected event
this.onRejectedCallbackList.forEach((fn) => fn())
}
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onFulfilled, onRejected) {
// onFulfilled and onRejected Is an optional parameter , If it is not transmitted, the default value should be set
onFulfilled =
typeof onFulfilled === 'function' ? onFulfilled : (value) => value
onRejected =
typeof onRejected === 'function'
? onRejected
: (reason) => {
throw reason
}
const promise2 = new MyPromise((resolve, reject) => {
// Determine which callback to execute according to the status
if (this.status === FULFILLED) {
// Execute as a macro task resolvePromise To ensure that you get promise2 example
setTimeout(() => {
try {
let x = onFulfilled(this.value)
// Handle x
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
}
if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = onRejected(this.reason)
// Handle x
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
}
if (this.status === PENDING) {
// pending In this state, you need “ subscribe ” fulfilled and rejected event
this.onFulfilledCallbackList.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value)
// Handle x
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
this.onRejectedCallbackList.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason)
// Handle x
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
}
})
return promise2
}
catch(onRejected) {
return this.then(null, onRejected)
}
}
Copy code
copyright notice
author[Straw hat plastic],Please bring the original link to reprint, thank you.
https://en.qdmana.com/2022/04/202204291313146671.html
The sidebar is recommended
- Element notify notification prompt text wrap
- Belkin: it's too early to talk about real wireless space charging
- JavaScript ES6 set (set) type* ω *
- Understand JavaScript function execution process and scope chain
- Java project: nursing home management system (java + springboot + thymeleaf + HTML + JS + MySQL)
- Java project: drug management system (java + springboot + HTML + layui + bootstrap + seals + MySQL)
- An error is reported when installing hexo during hexo deployment. What is the reason and how to solve it (tag git | keyword path)
- [front end basics] Linux command: Project Automation deployment
- Less than 100 or win Porsche, Maserati "special supply" 3.0T technical analysis
- Introduction to basic methods of math built-in objects in JavaScript
guess what you like
CSS matches the nth element of the parent element
What is the commercialization process of unmanned robotaxi after China allows driverless steering wheel?
A lot of trouble, cook also worried: a quarter less than $8 billion
Ajax realizes no refresh paging (no refresh paging shopping cart source code)
[flutter topic] 101 what is flutter elements Yyds dry goods inventory
What is the commercialization process of unmanned robotaxi after China allows driverless steering wheel?
A lot of trouble, cook also worried: a quarter less than $8 billion
Element acquisition of data structure c language sequence stack
Set video label based on Vue
CSS realizes div width adaptation, and the proportion of height and width is fixed
Random recommended
- How to create JavaScript custom events
- Is there any recommendation for wireless signal and wired signal power amplification circuit?
- Introduction to basic methods of math built-in objects in JavaScript
- 1000km pure electric endurance is meaningless? Then why does the car factory still try its best to extend the endurance?
- [let's implement a simple vite!] Chapter 4 - compiling single file components
- 3-11xss htmlspecialchars bypass demo
- How to conduct architecture design 𞓜 deeply reveal Alibaba cloud serverless kubernetes (2)
- Heapdump performance community special series 7: front door opener of large factory -- Application Practice of fluent
- 1、 HTML base tag
- Don't leave the crane unless necessary! A case of novel coronavirus nucleic acid positive was found in Heshan, Guangdong
- [Architect (Part 20)] self defined template of scaffold and summary of phase I
- How does JavaScript understand this algorithm
- [live review] openharmony knowledge empowerment lesson 2, phase 5 - becoming a community talent
- Understand the basic history and knowledge of HTTP
- Influence of lazy loading of Web front-end training on Web Performance
- Guangxi 2021 Guangxi landscape - blessing Lake Changshou Island
- Responsive gulp Chinese network of web front end
- Twaver-html5 basic learning (26) background
- CSS learning notes [3] floating, not separated from document flow, inheritance and stacking
- Webengine loading local html is invalid, and html is the dynamic address generated by JS, which can be solved
- Event handling of react
- Character coding knowledge that needs to be understood in front-end development
- 05. JavaScript operator
- 06. JavaScript statement
- Vue basics and some components
- Introduction to front-end Er excellent resources
- Node. Summary of methods of outputting console to command line in JS
- The beginning of passion, the ending of carelessness, python anti climbing, Jiageng, friends asked for MIHA's API and arranged y24 for him
- Technology sharing | test platform development - Vue router routing design for front-end development
- Node under windows JS installation detailed steps tutorial
- Layui built-in module (element common element operation)
- Excuse me, my eclipse Exe software has clearly opened to display the number of lines. Why is it useless
- It was revealed that Meila of Sea King 2 had less than ten minutes to appear, which was affected by negative news
- Vue element admin how to modify the height of component El tabs
- Bootstrap navigation bar drop-down menu does not take effect
- Vue Preview PDF file
- Geely joined the hybrid sedan market, and Toyota seemed even more hopeless
- Mustache template engine for exploring Vue source code
- Referer and referer policy and picture anti-theft chain
- Explain the "three handshakes" and "four waves" of TCP connection in detail