results
Results first: Passed the Promise A+ test
The execution logic is consistent with V8. The next problem has to do with V8Promise
The output is consistent0, 1, 2, 3, 4, 5, 6
.
MyPromise.resolve()
.then(() = > {
console.log(0);
return MyPromise.resolve(4);
})
.then((res) = > console.log(res))
MyPromise.resolve()
.then(() = > console.log(1))
.then(() = > console.log(2))
.then(() = > console.log(3))
.then(() = > console.log(5))
.then(() = > console.log(6))
Copy the code
The principle of
Let’s talk about what I think of as the promise principle.
- Subscription-based publishing.
- Subscribe to:
then
Subscribe.then(onFulfilled, onRejected)
- Release:
Resolve, reject
Release.new Promise(resolve => resolve(1))
- Subscribe to:
- Asynchronous.
- Microtasks
- We can get through
queueMicrotask
Create a microtask
- We can get through
- Microtasks
- Chain call.
- The status cannot be changed after confirmation.
implementation
MyPromise is implemented using es6’s class, private properties, arrow functions, and queueMicrotask. The logic refers to the V8 source implementation (read the source article in Resources below). The code is very simple, just look at it.
class MyPromise {
status = Pending // Initial Pending state
reaction = [] // Then subscription queue
value = undefined
constructor(executor) {
// callOnes ensures that resolve, reject is invalid after one of the functions is executed
const [resolve, reject] = callOnes(this.#resolve, this.#reject)
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
#resolve = (v) = > {
// Resolve is passed in multiple cases
this.#resolvePromise(v)
}
// Boolean state check function
#fulfil = (v) = > {
if (this.status === Pending) {
this.status = Fulfilled
this.value = v
this.#triggerPromiseReactions()
}
}
// Rejected State check function
#reject = (reason) = > {
if (this.status === Pending) {
this.status = Rejected
this.value = reason
this.#triggerPromiseReactions()
}
}
// Handle multiple cases of result in resolve
#resolvePromise = (result) = > {
/ / the result is this
if (this === result) {
return this.#reject(new TypeError('The promise and the return value are the same'))}// Direct assignment without object.
// Note that function counts as object and null counts as not
if(! isObject(result)) {return this.#fulfil(result);
}
let then;
try {
/ / then
then = result.then;
} catch (error) {
// Then throws an error
return this.#reject(error);
}
// Common objects, non-thenable objects
if (typeofthen ! = ='function') {
return this.#fulfil(result)
}
// result is myPromise or thenable
// EcMA stipulates that the next microtask is unified
queueMicrotask(() = > {
if (then === this.then) {
// Result is the value passed in, which is an instance of MyPromise.
// Verify the status and value of this with result.
// Why then? Because result might be in a Pending state.
result.then(this.#fulfil, this.#reject)
// Note that the microtask executes then, then triggers the microtask, where there are two microtasks execution time.
} else {
// is thenable (that is, the object with the "then" method)
const [resolve, reject] = callOnes(this.#resolve, this.#reject)
try {
// Execute this to thenable
then.call(result, resolve, reject)
} catch (error) {
reject(error)
}
}
})
}
// Subscribe ondepressing, onRejected
then(onFulfilled, onRejected) {
// Return a new promise
const promise = new MyPromise(() = > {})
// Save the subscription object subscribed to onFulfilled and onRejected
this.reaction.push({
onFulfilled,
onRejected,
promise, // Save the returned promise for the chained call
})
// Status confirmed, start publishing
if (this.status ! == Pending) {this.#triggerPromiseReactions()
}
return promise
}
// Publish: Execute all subscriptions to THEN and confirm the state of the promise returned by THEN.
#triggerPromiseReactions = () = > {
const reaction = this.reaction
this.reaction = []
const handlerKeyMap = {
[Fulfilled]: 'onFulfilled',
[Rejected]: 'onRejected',}const handlerKey = handlerKeyMap[this.status]
// Publish: Execute all subscriptions to THEN and confirm the state of the promise returned by THEN.
for (let {[handlerKey]: handler, promise} of reaction) {
// handler: fetch the corresponding subscription function according to status
// The promise: then method creates and returns
// microtasks that handle subscriptions to the THEN method
queueMicrotask(() = > {
if (typeofhandler ! = ='function') {
// then has no subscription function. eg: then(null,null)
// Verify the state and value of the promise.
switch (this.status) {
case Fulfilled:
return promise.#resolve(this.value)
case Rejected:
return promise.#reject(this.value)
}
} else {
// then has subscription functions. eg: then(()=>1))
// Verify the state and value of the Promise by executing the subscription function
try {
let result = handler(this.value)
return promise.#resolve(result)
} catch (e) {
return promise.#reject(e)
}
}
})
}
}
static resolve(v) {
// ecMA specifies that promise. resolve passes a Promise and returns it directly.
if (v instanceof MyPromise) { return v }
return new MyPromise((resolve) = > resolve(v))
}
static reject(reason) {
return new MyPromise((_, reject) = > reject(reason))
}
}
const Pending = 'Pending'
const Fulfilled = 'Fulfilled'
const Rejected = 'Rejected'
// Only one can be executed
function callOnes(a, b) {
let canCalled = true
function warp(fn) {
return (. args) = > {
if(canCalled) { fn(... args) } canCalled =false}}return [warp(a), warp(b)]
}
/ / determine the object
const isObject = v= >v ! = =null && typeof v === 'object' || typeof v === 'function'
Copy the code
Why is there no other method like catch?
Because Promise A+ only says that there are then methods. And all the other methods wrap the THEN methods. Example: the catch
class MyPromise {
catch(onReject) {
return this.then(null, onReject)
}
}
Copy the code
Promise A + test
Github, github.com/promises-ap… Configuration package. Json
{
"name": "promise"."version": "1.0.0"."description": ""."main": "index.js"."devDependencies": {
"promises-aplus-tests": "*"
},
"scripts": {
"test": "promises-aplus-tests src/promise.js"
},
"keywords": []."author": ""."license": "ISC"
}
Copy the code
To begin testing
npm run test
Copy the code
Perfect through
Is the execution time the same as V8
I found a few questions to test. You can replace MyPromise with Promise, and the output is the same.
MyPromise.resolve()
.then(() = > {
console.log(0);
throw Promise.resolve(4);
})
.catch((res) = > console.log(res))
.then(() = > console.log(7))
MyPromise.resolve()
.then(() = > console.log(1))
.then(() = > console.log(2))
.then(() = > console.log(3))
.then(() = > console.log(5))
.then(() = > console.log(6))
// Output: 0 1 2 3 4 5 6
Copy the code
let p1 = MyPromise.resolve()
.then(v= > console.log(1))
.then(v= > console.log(2))
.then(v= > console.log(3))
p1.then(v= > console.log(4))
p1.then(v= > console.log(5))
let p2 = MyPromise.resolve()
.then(v= > console.log(11))
.then(v= > console.log(22))
.then(v= > console.log(33))
p2.then(v= > console.log(44))
p2.then(v= > console.log(55))
// Output: 1 11 2 22 3 33 4 5 44 55
Copy the code
conclusion
Our MyPromise does the following:
- Passed the Promise A+ test.
- Microtask execution timing is consistent with V8.
The code is available on Github at github.com/liu-zhi-fei… .
The resources
- zhuanlan.zhihu.com/p/264944183
- zhuanlan.zhihu.com/p/329201628
- www.zhihu.com/question/45…
- Chromium.googlesource.com/v8/v8.git/+…
- Github.com/xianshenglu…
- tc39.es/ecma262/
- Github.com/promises-ap…