Write Promises by hand and pass Promises/A+ all tests
The first is in a wechat public account to see an article, there is a part of the handwritten Promise, feel very novel, is also a challenge, then I also want to try, the difference is that they are a common hand-written JS set of an article list, Promise is just a small piece of it, I want to carry it out alone, ha ha
Public account article
This article addresses
The basic concept
1.Promise
Promise is a solution to asynchronous programming that is more rational and powerful than the traditional solution of callbacks and events. It was first proposed and implemented by the community, and ES6 wrote it into the language standard, unifying usage and providing Promise objects natively.
A Promise is simply a container that holds the result of an event (usually an asynchronous operation) that will end in the future. Syntactically, a Promise is an object from which to get messages for asynchronous operations. Promise provides a uniform API so that various asynchronous operations can be handled in the same way.
The above is from Ruan Yifeng’s introduction to ECMAScript 6
2.Promises/A+
What are Promises/A+? Here is A sentence from Promises/A+ website
An open standard for sound, interoperable JavaScript promises — by implementers, for implementers.
Promises/A+ is the open standard for JavaScript Promises. Promises follow the basic standard. Promises are known as ES6 Promises that fully comply with Promises/A+ specifications. They are not identical, however. ES6 Promises complement catch, finally, static methods all, resolve, reject, and so on
3.Promises/A+?
At first, I wanted to list all the rules for Promises/A+, but after A while, it seemed like there was no need for it. Everyone interested can go to have a look at their own, I think the consult the original English, use Google translation can roughly basically accurate, but there are still a little bit of translation does not reach the designated position, is recommended here to look at the others translation of ready-made, I here looking for a someone else’s translation, personal feel translation also listen to good, There are notes to Promises/A+ translation
Write a Promise
Promise /A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises
Handwritten Promise code
const CustomPromise = class {
// Define a static map of all states
static STATUS_MAP = {
Pending: 'Pending'.Fulfilled: 'Fulfilled'.Rejected: 'Rejected',}// The state of the Promise
status = CustomPromise.STATUS_MAP.Pending
This is a list of onfulfilled functions passed to the then method
onfulfilled = []
// The list of onRejected functions passed in to the then method
onrejected = []
result = undefined
reason = undefined
// The list of the executor callback function resolve for the Promise parameter returned by the then method
resolve = []
// A list of the reject callback function for the Promise parameter returned by the then method
reject = []
// The list of promises returned by the then method
promises = []
/** * constructor *@param {function} Executor Promise Specifies the executor@returns* /
constructor (executor) {
if (typeof executor === 'undefined' || typeofexecutor ! = ='function') {
throw new TypeError('CustomPromise resolver is not a function')}This function will be fulfilled gradually. This function will be fulfilled gradually@param {*} result
*/
const setResult = (result) = > {
this.result = result
this.status = CustomPromise.STATUS_MAP.Fulfilled
if (this.onfulfilled.length > 0) {
this.onfulfilled.forEach((onfulfilled_item, index) = > {
this.excuteOnfulfilled(onfulfilled_item, index, this.result)
})
}
}
/** * set the reason you rejected and the onrejected function *@param {*} result
*/
const setReason= (reason) = > {
this.reason = reason
this.status = CustomPromise.STATUS_MAP.Rejected
if (this.onrejected.length > 0) {
this.onrejected.forEach((onrejected_item, index) = > {
this.excuteOnrejected(onrejected_item, index, this.reason)
})
}
}
try {
const resolve = (result) = > {
if (this.status === CustomPromise.STATUS_MAP.Pending) { // The internal state of the Promise has the effect of solidification, once determined no change
if(result ! = =null && (typeof result === 'function' || typeof result === 'object')) {
let called = false
try {
const { then } = result // The resolve method accepts a thenable object
if (typeof then === 'function') {
const then_ = then.bind(result)
then_(res= > {
if (called) return // Ensure that the resolvePromise callback for the then method on the thenable object is executed only once
called = true
setResult(res)
}, err= > {
if (called) return
called = true
setReason(err)
})
} else {
setResult(result)
}
} catch (error) {
if (called) return
setReason(error)
}
} else {
setResult(result)
}
}
}
const reject = (reason) = > {
if (this.status === CustomPromise.STATUS_MAP.Pending) {
setReason(reason)
}
}
const executor_ = executor.bind(null, resolve, reject) // Bind parameters to the actuator
executor_() // Executor execution (synchronization)
} catch (e) {
if (this.status === CustomPromise.STATUS_MAP.Fulfilled || this.status === CustomPromise.STATUS_MAP.Rejected) return
setReason(e)
}
}
/** * then method *@param {function} onfulfilled
* @param {function} onrejected
* @returns * /
then (onfulfilled, onrejected) {
this.onfulfilled.push(onfulfilled)
if (this.status === CustomPromise.STATUS_MAP.Fulfilled) { // Promise objects can still call the THEN method after the state is frozen
this.onfulfilled.forEach((item, index) = > {
if (item === onfulfilled) {
this.excuteOnfulfilled(item, index, this.result)
}
})
}
this.onrejected.push(onrejected)
if (this.status === CustomPromise.STATUS_MAP.Rejected) {
this.onrejected.forEach((item, index) = > {
if (item === onrejected) {
this.excuteOnrejected(item, index, this.reason)
}
})
}
const customPromise = new CustomPromise((resolve, reject) = > {
this.resolve.push(resolve)
this.reject.push(reject)
})
this.promises.push(customPromise)
return customPromise // The then method returns a new Promise object
}
This function will be fulfilled gradually. /** *@param {function} onfulfilled
* @param {number} index
* @param {*} result
*/
excuteOnfulfilled (onfulfilled, index, result) {
if (typeof onfulfilled === 'function') {
setTimeout(() = > {
let x = null
try {
x = onfulfilled(result)
} catch (error) {
this.reject[index](error)
}
if (x === this.promises[index]) {
this.reject[index](new TypeError('[onFulfilled] return the same value with [then] function'))}this.resolutionProcedure(x, this.promises[index], this.resolve[index], this.reject[index])
}, 0)}else {
if (this.status === CustomPromise.STATUS_MAP.Fulfilled) {
setTimeout(() = > {
this.resolve[index](result)
}, 0)}}}/** * execute the onrejected function@param {function} onrejected
* @param {number} index
* @param {*} reason
*/
excuteOnrejected (onrejected, index, reason) {
if (typeof onrejected === 'function') {
setTimeout(() = > {
let x = null
try {
x = onrejected(reason)
} catch (error) {
this.reject[index](error)
}
if (x === this.promises[index]) {
this.reject[index](new TypeError('[onrejected] return the same value with [then] function'))}this.resolutionProcedure(x, this.promises[index], this.resolve[index], this.reject[index])
}, 0)}else {
if (this.status === CustomPromise.STATUS_MAP.Rejected) {
setTimeout(() = > {
this.reject[index](reason)
}, 0)}}}/** * Promise process (focus) *@param {*} X the value returned by the resolvePromise function after execution@param {CustomPromise} The PROMISE * returned by the Promise THEN method@param {function} The callback function resolve * to the executor argument to the Promise returned by the resolve THEN method@param {function} Reject * callback to executor as the parameter to the Promise returned by the reject THEN method@returns * /
resolutionProcedure (x, promise, resolve, reject) {
if (x instanceof CustomPromise) {
x.then(res= > {
resolve(res)
}, err= > {
reject(err)
})
} else if(x ! = =null && (typeof x === 'function' || typeof x === 'object')) {
let called = false
try {
const { then } = x // The value returned by the resolvePromise function is a Thenable object, and the then method is executed
if (typeof then === 'function') {
const then_ = then.bind(x)
const resolvePromise = y= > {
if (called) return // Make sure the resolvePromise is executed only once
called = true
// The value returned by the resolvePromise function is a Thenable object, if the resolvePromise parameter is called back after the then method is executed
// continue to execute the Promise resolutionProcedure for the y parameter involved in the callback, that is, call the resolutionProcedure method
this.resolutionProcedure(y, promise, resolve, reject)
}
const rejectPromise = r= > {
if (called) return
called = true
reject(r)
}
then_(resolvePromise, rejectPromise)
} else {
resolve(x)
}
} catch (error) {
if (called) return
reject(error)
}
} else {
resolve(x)
}
}
/** * Static resolved method that returns a Promise that has been successful@param {*} result
* @returns * /
static resolved (result) {
return new CustomPromise((resolve, reject) = > {
if(result ! = =null && (typeof result === 'function' || typeof result === 'object')) {
let called = false
try {
const { then } = result
if (typeof then === 'function') {
const then_ = then.bind(result)
then_(res= > {
if (called) return
called = true
resolve(res)
}, err= > {
called = true
reject(err)
})
} else {
resolve(result)
}
} catch (error) {
if (called) return
reject(error)
}
} else {
resolve(result)
}
})
}
/** * returns a Promise that has failed@param {*} result
* @returns * /
static rejected (reason) {
return new CustomPromise((resolve, reject) = > {
reject(reason)
})
}
/ * * * *@returns Test with * /
static deferred () {
const result = {};
result.promise = new CustomPromise(function(resolve, reject) {
result.resolve = resolve;
result.reject = reject;
});
returnresult; }}module.exports = CustomPromise;
Copy the code
Write Promise notes
List a typical standard code that uses promises, and some of the following terms will be subject to this
const p = new Promise((resolve, reject) = > {
if (xxx) {
resolve()
} else {
reject(new TypeError('error'))}})/ / thenable object
const thenable = (val) = > {
return {
then: (resolvePromise, rejectPromise) = > {
// balabala
rejectPromise(val)
}
}
}
const onFulfilled = (res) = > {
return x
}
const onRejected = (err) = > {}
const p1 = p.then(onFulfilled, onRejected)
Copy the code
Here are a few points that I find easy to overlook when writing promises
Promise
The internal state has a solidification effect and will not change once it is determinedPromise
The constructor of is executed synchronouslyPromise
The inside of theresolve
Execution is synchronous, butPromise
When used,resolve
It could be called synchronously or asynchronously, so be aware of that.resolve
If it’s called synchronously,then
When the method executes, execute it immediatelyonFulfilled
As well asonRejected
Now,resolve
If it’s called asynchronously,then
Method is going to be executed firstonFulfilled
As well asonRejected
Put it away untilresolve
When it is called.resolve
You can pass in athenable
Object, if yesthenable
Object that needs to perform the actions shown in the code below (calling it)then
Methods)onFulfilled
As well asonRejected
inPromise
Internal needs to be called asynchronously (here let’s first directly understand as asynchronous can, in-depth talk also involvesMacro task micro task, interested in can go to understand, here I am direct usesetTimeout
Implement asynchronous, is can pass the test smoothlythen
Can be called multiple times (p.then(); p.then();
), can also be called chained (p.then().then();
), each time the THEN method returns a new onePromise
, soPromise
Internal design preservationonFulfilled
As well asonRejected
The data structure is an array- Very simple beep beep
Promise
Solution process:then
Method returns aPromise
callp1
In returnthen
thePromise
When we take the constructor argumentexecutor
Callback functionresolve
As well asreject
Put it in storage.onFulfilled
If I return athenable
Object (the one abovex
If notthenable
Object directlyresolve(x)
), to thisthenable
Object performs the actions shown in the code below (calling it)then
Methods). ifresolvePromise
It’s called, and the parameter we represent isy
If they
isthenable
Object to continue the following operation (if not directly)resolve(y)
), and so on, until you meety
notthenable
Object (this is just my own simple understanding, if the expression see confused also please withPromises/A+
Although I think that one might be more confusing…) - You need to deploy three static methods, static
resolved
Method (can also be passed inthenable
Object) to return a successfulPromise
; staticrejected
Method that returns a failed onePromise
The staticdeferred
Method that returns an object containing (aPromise
Object, create thisPromise
Object is a constructor parameterexecutor
Callback functionresolve
As well asreject
).resolved
Methods andrejected
Method deployment is not mandatory - ** Important: ** Constructor parameters
executor
Callback functionresolve
And staticresolved
Either way is acceptablethenable
Object as an argumentthenable
Object performs the actions shown in the code below (calling it)then
Method), this isPromises/A+
What the specification does not specify, it is ignored in the first place, which leads to the failure of the test to proceed smoothly
const resolvePromise = (y) = > {
// balabala
}
const rejectPromise = (err) = > {
// balabala
}
thenable.then(resolvePromise, rejectPromise)
Copy the code
Write Promise in-line tests using promises-tests
The test steps are simple
- Install dependencies
npm install promises-aplus-tests --save-dev
Copy the code
package.json
Join the script
"test": "Promises -aplus-tests < Your handwritten Promise JS path >"
Copy the code
- Console input
npm run test
Copy the code
- Waiting for the results…
Rest assured, it’s not going to be pretty at first, just like me… 😂
In the end 😉
Debug guide
If you are looking at the code over and over again, you will find that there are still a few errors in the code you are testing. If you are looking at the code, you will find that there is no danger of losing the test. If you are looking at the code, you will find that there is no danger of losing the test. You don’t know how to pass a test until you know what’s on it, do you? (As if the metaphor is strange 😅)
Promises – Tests repository address
Clone the project first and then open it to see what the entry is
"bin": "lib/cli.js".Copy the code
We openlib/cli.js
It turns out that its main function is right herelib/programmaticRunner.js
promises-tests
Using aMocha
Conducted tests in the test filelib/tests
Next, he will read the test files and test them in turn
You can locate your own failed test cases based on the error message of the test
Most of my tests fell on 2.3.3.3. We opened 2.3.3 and found that it contains one main test case with three more test cases in it
Open them one by one until 2.3.3.3
Extract the main test code
- PromiseTest.js
import thenables from './thenables';
import CustomPromise from '.. /promise/CustomPromise';
const resolved = CustomPromise.resolved;
const rejected = CustomPromise.rejected;
const sentinel = { sentinel: "sentinel" };
const dummy = { dummy: "dummy" };
export default function testMain () {
function yFactory() {
return thenables.fulfilled['an already-fulfilled promise'](thenables.fulfilled['an asynchronously-fulfilled custom thenable'](sentinel));
}
function xFactory() {
return {
then: function (resolvePromise) {
constyFactory_ = yFactory() resolvePromise(yFactory_); }}; }const promise = resolved(dummy).then(function onBasePromiseFulfilled() {
const xFactory_ = xFactory()
return xFactory_;
});
const test = function (promise) {
promise.then(function onPromiseFulfilled(value) {
console.log('At last:', value);
})
}
test(promise)
}
Copy the code
- thenables.js
import CustomPromise from './CustomPromise';
var resolved = CustomPromise.resolved;
var rejected = CustomPromise.rejected;
var deferred = CustomPromise.deferred;
var other = { other: "other" }; // a value we don't want to be strict equal to
const fulfilled = {
"a synchronously-fulfilled custom thenable": function (value) {
return {
then: function (onFulfilled) { onFulfilled(value); }}; },/ / a little
};
const rejected_ = {
"a synchronously-rejected custom thenable": function (reason) {
return {
then: function (onFulfilled, onRejected) { onRejected(reason); }}; },/ / a little
};
export default {
fulfilled: fulfilled,
rejected: rejected_
}
Copy the code
This will be fulfilled fulfilled someday. In this case, if you can output {sentinel: “sentinel”}, it will mean that the test has passed. Then you can debug your program based on this small test code. 🤗
The meaning of a handwritten Promise
- The first is being able to write by hand
Promise
rightPromise
To have a deeper understanding, like I had no idea beforeonFulfilled
returnthenable
Of course, this implementation is still very rudimentary, such asES6
Promise
The instancecatch
,finally
, static methodall
,any
As well asrace
We haven’t done that yet. We can add it later - Secondly, the interview is now easily build aircraft carriers, do not learn something 😷
Next up
If go online smoothly should push down my small program, welcome everyone to try 🤗