This is the 7th day of my participation in the August Text Challenge.More challenges in August
The Promise object is used to represent the final state (success/failure) of an asynchronous operation and its resulting value. – the MDN
The advent of Promises allowed us to gracefully handle asynchronous operations without the pain of callback hell. Every good has its bad, it becomes one of the interview questions, and turns into another kind of pain living around us… Of course, this is a joke ~ 🤣
The last time I saw Promise was about A month ago, and I spent two days in my spare time writing it by hand based on Promise/A+. It was cloudy and foggy. In order to consolidate while weekend wrote again, thoroughly comb clear ✌. This article is mainly dismantling the implementation of Promise beggar version and its periphery.
The basic characteristics of
- New promises are executed immediately and cannot be interrupted.
- Promise has three states:
pengding
,fulfilled
,rejected
. onlypengding -> fulfilled
andpending -> rejected
Two state flows, and cannot be changed again after the state changes. resolve
Is the successful state;reject
Is in the failed state.
Promise basically uses 👇
const p = new Promise((resolve, reject) = > {
resolve('success')
reject('error')
})
p.then(value= > {
console.log(value)
}, reason= > {
console.log(reason)
})
// success
Copy the code
Dismantlement implement Promise beggar version
Implement basic logic
// promise.js
const PENGDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function Promise(executor) {
let that = this
// The initial state is pengding
this.status = PENGDING
// Record the result value returned successfully
this.value = null
// Record the result value returned by the failure
this.reason = null
function resolve(value) {
if (that.status === PENGDING) {
that.status = FULFILLED
that.value = value
}
}
function reject(reason) {
if (that.status === PENGDING) {
that.status = REJECTED
that.reason = reason
}
}
executor(resolve, reject)
}
Promise.prototype.then = function (onFulfilled, onRejected) {
if (this.status === FULFILLED) {
// The callback succeeded
onFulfilled(this.value)
} else if (this.status === REJECTED) {
// Call failed callback
onRejected(this.reason)
}
}
module.exports = Promise
Copy the code
Introduce the implementation of the beggar version Promise, perform below 🌰
// main.js
const Promise = require('./promise')
const p = new Promise((resolve, reject) = > {
resolve('success')
reject('error')
})
p.then(
(value) = > {
console.log(value)
},
(reason) = > {
console.log(reason)
}
)
Copy the code
Execute to print success.
Handling asynchronous cases
// main.js
const Promise = require('./promise')
const p = new Promise((resolve, reject) = > {
setTimeout(() = > {
resolve('success')
})
})
p.then(
(value) = > {
console.log(value)
},
(reason) = > {
console.log(reason)
}
)
Copy the code
This. Status of the then function is found to be pengding. Why is that? For those of you who know about event loops, setTimeout is asynchronous and is a macro task that will be executed in the next event loop macro task, and p.teng is the synchronization code for the current macro task. Therefore, thate. status is still pengding, so there is no output.
In this case, we need to add two fields ondepressing and onRejected to cache the successful and failed callbacks, and then execute the cache function resolve or Reject.
// promise.js
const PENGDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function Promise(executor) {
let that = this
// The initial state is pengding
this.status = PENGDING
// Record the result value returned successfully
this.value = null
// Record the result value returned by the failure
this.reason = null
this.onFulfilled = null
this.onRejected = null
function resolve(value) {
if (that.status === PENGDING) {
that.status = FULFILLED
that.value = value
// If a cache callback succeeds, execute
that.onFulfilled && that.onFulfilled(value)
}
}
function reject(reason) {
if (that.status === PENGDING) {
that.status = REJECTED
that.reason = reason
// There is a cache failure callback
that.onRejected && that.onRejected(value)
}
}
executor(resolve, reject)
}
Promise.prototype.then = function (onFulfilled, onRejected) {
if (this.status === FULFILLED) {
// The callback succeeded
onFulfilled(this.value)
} else if (this.status === REJECTED) {
// Call failed callback
onRejected(this.reason)
} else {
// Cache success and failure callback in mount state
this.onFulfilled = onFulfilled
this.onRejected = onRejected
}
}
module.exports = Promise
Copy the code
After the improvement, execute the above asynchronous case code and print success successfully.
In the case of asynchrony, add more ingredients 🍕, execute different P. Chen functions many times.
// main.js
const Promise = require('./promise')
const p = new Promise((resolve, reject) = > {
setTimeout(() = > {
resolve('success')
})
})
p.then(
(value) = > {
console.log('First time', value)
},
(reason) = > {
console.log(reason)
}
)
p.then(
(value) = > {
console.log('The second time', value)
},
(reason) = > {
console.log(reason)
}
)
// The second success
Copy the code
Found that our first P.chen code was not executed. To analyze the reason: currently we only use one variable to store successful and failed callbacks. When I execute setTimeout, my two p.chen are executed in order. The second p.teng will override the callback assigned by the first p.teng, so the result is the second success.
To improve the code, we use arrays to store all callbacks.
const PENGDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function Promise(executor) {
let that = this
// The initial state is pengding
this.status = PENGDING
// Record the result value returned successfully
this.value = null
// Record the result value returned by the failure
this.reason = null
// Array records all successful callbacks
this.onFulfilled = []
// Array records all failed callbacks
this.onRejected = []
function resolve(value) {
if (that.status === PENGDING) {
that.status = FULFILLED
that.value = value
// If a cache callback succeeds, execute
that.onFulfilled.forEach((fn) = > fn(value))
}
}
function reject(reason) {
if (that.status === PENGDING) {
that.status = REJECTED
that.reason = reason
// There is a cache failure callback
that.REJECTED.forEach((fn) = > fn(reason))
}
}
executor(resolve, reject)
}
Promise.prototype.then = function (onFulfilled, onRejected) {
if (this.status === FULFILLED) {
// The callback succeeded
onFulfilled(this.value)
} else if (this.status === REJECTED) {
// Call failed callback
onRejected(this.reason)
} else {
// All success and failure callbacks are cached in mount state
this.onFulfilled.push(onFulfilled)
this.onRejected.push(onRejected)
}
}
module.exports = Promise
Copy the code
Handling chain calls and value penetration (synchronization)
The focus of Promise is on chained invocation, which handles three cases. According to the Promise/A+ idea, each time A Promise. Then is executed, A new Promise is created and the return value of the previous THEN is passed to the then method of the next Promise to achieve chain calls and value penetration. The same applies to resolve and reject.
// main.js
const Promise = require('./promise')
const p = new Promise((resolve, reject) = > {
// Only synchronous chain calls are currently handled
resolve('success')})// Return the normal value
p.then((value) = > {
return value
}).then((value) = > {
console.log(value)
})
// Cannot read property 'then' of undefined
/ / return promise
p.then((value) = > {
return new Promise((resolve, reject) = > {
resolve(value)
})
}).then((value) = > {
console.log(value)
})
// Cannot read property 'then' of undefined
/ / value through
p.then().then((value) = > {
console.log(value)
})
// Cannot read property 'then' of undefined
Copy the code
The main parts of the improved code are as follows.
.Promise.prototype.then = function (onFulfilled, onRejected) {
let that = this
// Value penetration problem
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) = > value
onRejected = typeof onRejected === 'function' ? onRejected : (reason) = > { throw reason }
const p2 = new Promise((resolve, reject) = > {
if (that.status === FULFILLED) {
// The callback succeeded
const x = onFulfilled(that.value)
resolvePromise(x, resolve, reject)
} else if (that.status === REJECTED) {
// Call failed callback
const x = onRejected(that.reason)
resolvePromise(x, resolve, reject)
} else {
// All success and failure callbacks are cached in mount state
that.onFulfilled.push(onFulfilled)
that.onRejected.push(onRejected)
}
})
return p2
}
function resolvePromise(x, resolve, reject) {
// If x is a Promise object, execute its then function (callback of p2, not callback of x returned)
if (x instanceof Promise) {
x.then(
(value) = > resolve(value),
(reason) = > reject(reason)
)
} else {
// If x is normal
resolve(x)
}
}
module.exports = Promise
Copy the code
Continuing with the code in all three of the above examples results in success.
- If the value is normal, directly
resolve
The return valuex
. - If it is
Promise
, the implementation ofx.then
. Attention!!then
The function pass parameter isp2
theresolve
andreject
, so executeconst x = onFulfilled(that.value)
Is executedp2
theresolve
Function to pass the value along. - Value penetrates at no
resolve
andreject
Parameter to determine whether the input parameter is a function. If not, assign the default function.
Handling chain calls and value penetration (asynchronous)
Change the logic in main.js to asynchronous.
// main.js
const Promise = require('./promise')
const p = new Promise((resolve, reject) = > {
setTimeout(() = > {
resolve('success')})})...Copy the code
First, p.teng and p.teng. Then are the first round of event loops, so they will be executed at setTimeout. Second, in the asynchronous case, we cache success and callback functions and wait for resolve or reject to execute. But we’re only caching the callback function, not a promise object, so we just need to wrap one more layer.
// promise.js
Promise.prototype.then = function (onFulfilled, onRejected) {...const p2 = new Promise((resolve, reject) = > {
if (that.status === FULFILLED) {
...
} else if (that.status === REJECTED) {
...
} else {
// All success and failure callbacks are cached in mount state
that.onFulfilled.push(() = > {
const x = onFulfilled(that.value)
resolvePromise(x, resolve, reject)
})
that.onRejected.push(() = > {
const x = onRejected(that.reason)
resolvePromise(x, resolve, reject)
})
}
})
return p2
}
Copy the code
Handle the case of returning itself
Native promises throw Chaining cycle detected for promise #< promise > that returns errors equal to itself.
const p = new Promise((resolve, reject) = > {
resolve('success')})const p1 = p.then((value) = > {
console.log(value)
return p1
})
Copy the code
ResolvePromise adds p2 pass-through and judgment
Promise.prototype.then = function (onFulfilled, onRejected) {
let that = this
// Value penetration problem
onFulfilled =
typeof onFulfilled === 'function' ? onFulfilled : (value) = > value
onRejected =
typeof onRejected === 'function'
? onRejected
: (reason) = > {
throw reason
}
const p2 = new Promise((resolve, reject) = > {
if (that.status === FULFILLED) {
// The callback succeeded
const x = onFulfilled(that.value)
resolvePromise(p2, x, resolve, reject)
} else if (that.status === REJECTED) {
// Call failed callback
const x = onRejected(that.reason)
resolvePromise(p2, x, resolve, reject)
} else {
// All success and failure callbacks are cached in mount state
that.onFulfilled.push(() = > {
const x = onFulfilled(that.value)
resolvePromise(p2, x, resolve, reject)
})
that.onRejected.push(() = > {
const x = onRejected(that.reason)
resolvePromise(p2, x, resolve, reject)
})
}
})
return p2
}
function resolvePromise(p2, x, resolve, reject) {
// If return itself throws an error
if (x === p2)
reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
If x is a promise object
if (x instanceof Promise) {
x.then(
(value) = > resolve(value),
(reason) = > reject(reason)
)
} else {
// If x is normal
resolve(x)
}
}
Copy the code
Perform errorp1 is not defined
View stack error due toresolvePromise(p2, x, resolve, reject)
In thep2
Not initialized.
According to thepromise/A+
The specification suggests that macro/micro tasks can be skillfully used to solve this problem, here I choosesetTimeout
The form of macro tasks. The change code is as follows:
Promise.prototype.then = function (onFulfilled, onRejected) {
let that = this
// Value penetration problem
onFulfilled =
typeof onFulfilled === 'function' ? onFulfilled : (value) = > value
onRejected =
typeof onRejected === 'function'
? onRejected
: (reason) = > {
throw reason
}
const p2 = new Promise((resolve, reject) = > {
if (that.status === FULFILLED) {
// The callback succeeded
setTimeout(() = > {
const x = onFulfilled(that.value)
resolvePromise(p2, x, resolve, reject)
})
} else if (that.status === REJECTED) {
// Call failed callback
setTimeout(() = > {
const x = onRejected(that.reason)
resolvePromise(p2, x, resolve, reject)
})
} else {
// All success and failure callbacks are cached in mount state
that.onFulfilled.push(() = > {
setTimeout(() = > {
const x = onFulfilled(that.value)
resolvePromise(p2, x, resolve, reject)
})
})
that.onRejected.push(() = > {
setTimeout(() = > {
const x = onRejected(that.reason)
resolvePromise(p2, x, resolve, reject)
})
})
}
})
return p2
}
Copy the code
Add error handling
It mainly catches errors in constructors and then functions. Catch with try/catch
const PENGDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function Promise(executor) {
let that = this
// The initial state is pengding
this.status = PENGDING
// Record the result value returned successfully
this.value = null
// Record the result value returned by the failure
this.reason = null
// Array records all successful callbacks
this.onFulfilled = []
// Array records all failed callbacks
this.onRejected = []
function resolve(value) {
if (that.status === PENGDING) {
that.status = FULFILLED
that.value = value
// If a cache callback succeeds, execute
that.onFulfilled.forEach((fn) = > fn(value))
}
}
function reject(reason) {
if (that.status === PENGDING) {
that.status = REJECTED
that.reason = reason
// There is a cache failure callback
that.onRejected.forEach((fn) = > fn(reason))
}
}
The constructor caught an error
try {
executor(resolve, reject)
} catch (err) {
reject(err)
}
}
Promise.prototype.then = function (onFulfilled, onRejected) {
let that = this
// Value penetration problem
onFulfilled =
typeof onFulfilled === 'function' ? onFulfilled : (value) = > value
onRejected =
typeof onRejected === 'function'
? onRejected
: (reason) = > {
throw reason
}
const p2 = new Promise((resolve, reject) = > {
if (that.status === FULFILLED) {
// The callback succeeded
setTimeout(() = > {
try {
const x = onFulfilled(that.value)
resolvePromise(p2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
} else if (that.status === REJECTED) {
// Call failed callback
setTimeout(() = > {
try {
const x = onRejected(that.reason)
resolvePromise(p2, x, resolve, reject)
} catch (err) {
reject(err)
}
} else {
// All success and failure callbacks are cached in mount state
that.onFulfilled.push(() = > {
setTimeout(() = > {
try {
const x = onFulfilled(that.value)
resolvePromise(p2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
})
that.onRejected.push(() = > {
setTimeout(() = > {
try {
const x = onRejected(that.reason)
resolvePromise(p2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
})
}
})
return p2
}
function resolvePromise(p2, x, resolve, reject) {
// If return itself throws an error
if (x === p2)
reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
If x is a promise object
if (x instanceof Promise) {
x.then(
(value) = > resolve(value),
(reason) = > reject(reason)
)
} else {
// If x is normal
resolve(x)
}
}
module.exports = Promise
Copy the code
Take 🌰 to verify
//main.js
const Promise = require('./promise')
const p = new Promise((resolve, reject) = > {
throw new Error('failed')
})
p.then(
(value) = > {
console.log(value)
return p1
},
(reason) = > {
console.log(reason)
}
)
// Error: failed
Copy the code
The standard version Promise
This version of the Promise has basically satisfied all cases, but always want to have A certificate, that also has to comply with our Promise/A+ specification.
npm init
npm install promises-aplus-tests --save-dev
- Methods to add packages
Promise.deferred = function () {
var result = {}
result.promise = new Promise(function (resolve, reject) {
result.resolve = resolve
result.reject = reject
})
return result
}
Copy the code
package.json
thescripts
The field change command istest: promises-aplus-tests promise
And performnpm run test
.
Execution, as expected, a bunch of errors. Change the resolvePromise function according to the specification as prompted
function resolvePromise(p2, x, resolve, reject) {
// If return itself throws an error
if (p2 === x) {
reject(new TypeError('Chaining cycle detected for promise #<Promise>'))}// Execute x.chen ().
if ((x && typeof x === 'object') | |typeof x === 'function') {
// Avoid calling the same callback more than once
let called = false
try {
let then = x.then
if (typeof then === 'function') {
then.call(
x,
(y) = > {
if (called) return
called = true
resolvePromise(p2, y, resolve, reject)
},
(r) = > {
if (called) return
called = true
reject(r)
}
)
} else {
// resolve(x)
if (called) return
called = true
resolve(x)
}
} catch (err) {
Error reject(err)
if (called) return
called = true
reject(err)
}
} else {
// if x is not an object or function (including if x is null)
resolve(x)
}
}
Copy the code
At this point, the standard Promise has changed.
Surrounding the Promise
Promise.resolve
Promise.resolve(value)
Returns a parsed Promise object for a given value.
- If the current value is
promise
Object, return thispromise
. - If it’s a
then
Function, which takes its final state. - Returns the current value as completed
promise
.
Handwritten implementation
Promise.resolve = function (param) {
if (param instanceof Promise) {
return param
}
return new Promise((resolve, reject) = > {
if (
param &&
typeof param === 'object' &&
typeof param.then === 'function'
) {
param.then(resolve, reject)
} else {
resolve(param)
}
})
}
Copy the code
Promise.reject
Return a Promise object with a reason for the rejection.
Basic implementation
Promise.reject = function(reason) {
return new Promise((resolve, reject) = > {
reject(reason)
})
}
Copy the code
Promise.all
Promise.all(iterable)
Promise.all takes input from an iterable of promises (Array, Map, Set) and returns only one Promise instance. The instance’s resolve is executed after all the Promise’s resolve callbacks end. Reject is an error thrown immediately after any promise is executed.
The basic use
const p1 = 'promise'
const p2 = new Promise((resolve, reject) = > {
resolve('success')})Promise.all([p1, p2].then((values) = > {
console.log(values) // ['promise', 'success']
}))
Copy the code
Handwritten implementation
Promise.all = function (params) {
const promises = Array.from(params)
let values = []
return new Promise((resolve, reject) = > {
if(! params.length) resolve(values) promises.forEach((promise, index) = > {
// Return values after all execution
if (index === promises.length - 1) resolve(values)
Promise.resolve(promise).then(
(value) = > {
console.log(value)
values[index] = value
},
Reject any error, reject
(reason) = > {
reject(reason)
}
)
})
})
}
Copy the code
Promise.race
Returns a promise that is resolved or rejected once a promise in the iterator is resolved or rejected.
The basic use
const p1 = new Promise((resolve, reject) = > {
setTimeout(() = > {
resolve('p1, success')},100)})const p2 = new Promise((resolve, reject) = > {
setTimeout(() = > {
resolve('p2, success')},200)})Promise.race([p1, p2]).then((value) = > {
console.log(value)
})
Copy the code
Handwritten implementation
Promise.race = function (params) {
const promises = Array.from(params)
return new Promise((resolve, reject) = > {
if(! promises.length)return
promises.forEach((promise) = > {
Promise.resolve(promise).then(
(value) = > {
resolve(value)
return
},
(reason) = > {
reject(reason)
return})})})}Copy the code
conclusion
If you feel helpful, don’t hesitate to go to 💕. In the next chapter, I will dig into the event cycle, add a concern with interest, and study together. 👀