Promise series
- Promise – Three questions of the soul
- Promise – Handwritten source code
- Promise – Fully functional
The previous article provided a brief look at the USE of the Promise API with some documentation and examples. In this article, we will use JavaScript to implement A simple program that complies with the Promise A+ protocol, based on some of the protocol points summarized in the previous article.
The source code to write
Implement a synchronous Promise framework
- Promise is a class that has three states (pending, fulfilled, rejected)
- When the Promise constructor is initialized, we pass executor, a function that will execute by default. We pass resolve/reject
- The default state of Promise is Pending
- When the executor arguments resolve/reject are called, the current state is modified and the success value/reason is recorded.
- Only the pending state can be modified to depressing/Rejected. If the state is fulfilled/ Rejected, the current state cannot be modified
- Each Priomise must provide a THEN method. By default, two callback methods are passed in, which is controlled by the program. OnFulfilled is called by default when the Fulfilled state is fulfilled and onRejected is called when the Fulfilled state is fulfilled
// Promise has three states: wait, success, and reject
const STATUS = {
PENDING: 'pending'.FULFILLED: 'fulfilled'.REJECTED: 'rejected',}class Promise {
constructor(executor) {
// Promise defaults to Pending
this.status = STATUS.PENDING
this.value = undefined // Store success values for subsequent chain calls
this.reason = undefined // Store the cause of the failure to facilitate subsequent chain calls
// The user calls the resolve function to change the state of the current instance when executing the function correctly. The resolve function supports passing in an argument as a success value
const resolve = (value) = > {
// The promise state can be changed only in the wait state
if (this.status === STATUS.PENDING) {
// Change the instance status to successful state
this.status = STATUS.FULFILLED
// Accept the success value from the user
this.value = value
}
}
Reject is called by the user to modify the state of the current instance when an error occurs or an exception is thrown. Reject supports passing in a parameter as the cause of failure or rejection
const reject = (reason) = > {
// Promise can only be modified in the wait state
if (this.status === STATUS.PENDING) {
// Change the state of the instance to failed
this.status = STATUS.REJECTED
// Accept the failure cause sent by the user
this.reason = reason
}
}
try {
// New Promise() passes in a function that executes by default and takes two executable method arguments
executor(resolve, reject)
} catch (error) {
// If the default function fails, the state is set to reject
reject(error)
}
}
This is a big pity; /** * 1. Each Priomise must provide a THEN method, which will be called onFulfilled by default. OnFulfilled is a big pity, and onRejected is called * when this state is fulfilled@param {*} Ondepressing successfully performs the function *@param {*} OnRejected fails to execute the function */
then(onFulfilled, onRejected) {
if (this.status === STATUS.FULFILLED) {
onFulfilled(this.value)
}
if (this.status === STATUS.FULFILLED) {
onRejected(this.reason)
}
}
}
module.exports = Promise
Copy the code
We implemented a Promise base shelf, but as a class that exists primarily to handle asynchronous operations, it wouldn’t work if we added asynchrony at this point. Now let’s add asynchrony
Implement the asynchronous invocation of Promise
If the Executor method is asynchronous, we will save the onFulfilled and onRejected methods when the STATUS is pending in THEN, and then save the method call when the state is modified.
constructor(executor){
// ...
// If the state does not change and then is called, we need to store the successful/failed methods in then and wait until the state changes
this.onFulfilledCallbacks = [] // Then's set of successful methods
this.onRejectedCallbacks = [] // Set of failed methods for then
const resolve = (val) = > {
if(this.status === STATUS.PENDDING) {
// ...
// After the state changes, the successful method set call then is initiated to implement asynchronous operation
this.onFulfilledCallbacks.forEach((fn) = > fn())
}
}
const reject = (reason) = > {
if(this.status === STATUS.PENDDING) {
...
// After the state changes, then failed method set calls are initiated to implement asynchronous operation
this.onRejectedCallbacks.forEach((fn) = > fn())
}
}
// ...
}
then(onFulfilled,onReject) {
// ...
// When the state of a promise is PENDING, it is an asynchronous operation
// Publish the resolve and reject methods in publish-subscribe mode and invoke them when subsequent state changes are complete
if (this.status === STATUS.PENDING) {
// Use decorator pattern/slicing to wrap the success/failure methods so that we can do other operations and get value/ Reason after state changes
this.onFulfilledCallbacks.push(() = > {
onFulfilled(this.value)
})
this.onRejectedCallbacks.push(() = > {
onRejected(this.reason)
})
}
}
// ...
Copy the code
At this point, we’ve basically implemented a simple Promise single then method call. But Promise’s main satisfence-enhancing then chain calls aren’t available yet, so let’s add the chain calls
Further implementation of the then() chain call
- Each Promise prototype has a THEN method on top, and the THEN method provides two executable method parameters
- The then method must return a promise, which we do by new a new promise
- Then Specifies the call rules for subsequent “THEN” in chain calls
- If an exception is thrown while calling a current successful/failed method, it will go to the next failed method
- Get the success/failure return value x
- If x is the underlying data type, it goes into the successful method of the next THEN (the successful method of the then is called whether it succeeds or fails).
- If x is an object
- X is a generic object that goes straight to the successful method of the next THEN
- X is a Promise, get the THEN of x, keep calling the THEN until you get the normal object or the underlying data type
- The successful and failed methods of the THEN of the promise2 call the resolve/ Reject methods corresponding to the promise
- The success or failure of the next THEN depends on the success or failure of X
- Subsequent chained calls to THEN will proceed to the next failure only if this exception is thrown or the Promise returned by the callback method fails. Otherwise, the next resolve method is used
then(onFulfilled, onRejected) {
// Create a new promise2 and return it at the end of the method, then reaches the chain call, continuing then is actually calling the new promise instance
const promise2 = new Promise((resolve, reject) = > {
// When we call the then method, we call different pass-the-argument methods by determining the current Promise state
// When the promise state is Fulfilled, the onFulfilled method is called and the success value is passed in as the parameter
if (this.status === STATUS.FULFILLED) {
// We need to handle promise2 and x, but promise2 is assigned after new, so we need to use an asynchronous macro/micro to get the promise
setTimeout(() = > {
try {
const x = onFulfilled(this.value)
// We use an external method to unify the processing here
resolvePromise(x, promise2, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)}// When the promise status is REJECTED, call the onRejected method and pass the failure reason as an argument
if (this.status === STATUS.REJECTED) {
setTimeout(() = > {
try {
const x = onRejected(this.reason)
// We use an external method to unify the processing here
resolvePromise(x, promise2, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)}// When the state of a promise is PENDING, it is an asynchronous operation
// Publish the resolve and reject methods in publish-subscribe mode and invoke them when subsequent state changes are complete
if (this.status === STATUS.PENDING) {
// Use decorator pattern/slicing to wrap the success/failure methods so that we can do other operations and get value/ Reason after state changes
this.onFulfilledCallbacks.push(() = > {
setTimeout(() = > {
try {
const x = onFulfilled(this.value)
// We use an external method to unify the processing here
resolvePromise(x, promise2, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)})this.onRejectedCallbacks.push(() = > {
setTimeout(() = > {
try {
const x = onRejected(this.reason)
// We use an external method to unify the processing here
resolvePromise(x, promise2, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)})}})return promise2
}
}
Copy the code
The method handling for success or failure callback is the same in all states, so a tool method is provided for unified use
// Handle subsequent state changes to the Promise(promise2) returned in then
function resolvePromise(x, promise2, resolve, reject) {
// If promise2 is passed in by itself
if (x === promise2) {
return reject(new TypeError('Repeat call'))}// Determine if the passed x is an object or method
if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
// In order to be compatible with other promises, add an anti-repetition feature to prevent other promises from being called repeatedly
let called
Reject (x) if an exception is thrown
try {
// Get the then attribute of x
const then = x.then
// If then is function, we default x to Promise
if (typeof then === 'function') {
// Call the then method of x, and call the corresponding success and failure if x succeeds/fails internally
then.call(
x,
(y) = > {
if (called) return
called = true
// If y is still a Promise, then needs to be resolved again to know that it is not a Promise
// resolve(y)
resolvePromise(y, promise2, resolve, reject)
},
(r) = > {
if (called) return
called = true
reject(r)
}
)
} else {
// If then is not a function, we default x to a non-promise object. Resolve (x)
resolve(x)
}
} catch (error) {
if (called) return
called = true
reject(error)
}
} else {
Resolve (x) if the value passed is a primitive value type
resolve(x)
}
}
Copy the code
Handle empty arguments for THEN
This will be a big pity. This will be a big pity. This will be a big pity. {}) such code. We deal with this situation.
then(onFulfilled, onRejected) {
// If onFulfilled/onRejected cannot obtain the current success/failure value to the next THEN
// New promise((resolve,reject) =>{resolve(1)}).then().then().then(of=> {console.log(of)}) needs to be able to pass in the value of
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : data= > data
onRejected = typeof onRejected === 'function' ? onRejected : e= > {throw e}
// ...
}
Copy the code
The resolve method passes in a Promise
If the consumer passes in a Promise when the constructor initializes, we need to use the success or failure of the Promise as the success/failure of our Promise
constructor(executor) {
/ /...
const resolve = (value) = > {
// Determine if the value passed in is a Promise. If it is still a Promise, return the call to THEN directly, passing the success and failure callbacks of resolve/reject to then. If the Promise succeeds/fails, modify the current state of the Promise with the callback argument
if (value instanceof Promise) {
return value.then(resolve, reject)
}
/ /...}}Copy the code
test
Use the Promise A+ link to find Promises – aplus-Tests and test this case. If successful, it means that our Promise meets the protocol standards and can be mixed
Promise.defer = Promise.deferred = function () {
let dfd = {}
dfd.promise = new Promise((resolve, reject) = > {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
Copy the code
The final code
// Promise has three states: wait, success, and reject
const STATUS = {
PENDING: 'PENDING'.FULFILLED: 'FULFILLED'.REJECTED: 'REJECTED',}class Promise {
constructor(executor) {
// Promise defaults to Pending
this.status = STATUS.PENDING
this.value = undefined // Store success values for subsequent chain calls
this.reason = undefined // Store the cause of the failure to facilitate subsequent chain calls
// If the state does not change and then is called, we need to store the successful/failed methods in then and wait until the state changes
this.onFulfilledCallbacks = [] // Then's set of successful methods
this.onRejectedCallbacks = [] // Set of failed methods for then
// The user calls the resolve function to change the state of the current instance when executing the function correctly. The resolve function supports passing in an argument as a success value
const resolve = (value) = > {
// Determine if the value passed in is a Promise. If it is still a Promise, return the call to THEN directly, passing the success and failure callbacks of resolve/reject to then. If the Promise succeeds/fails, modify the current state of the Promise with the callback argument
if (value instanceof Promise) {
return value.then(resolve, reject)
}
// The promise state can be changed only in the wait state
if (this.status === STATUS.PENDING) {
// Change the instance status to successful state
this.status = STATUS.FULFILLED
// Accept the success value from the user
this.value = value
// After the state changes, the successful method set call then is initiated to implement asynchronous operation
this.onFulfilledCallbacks.forEach((fn) = > fn())
}
}
Reject is called by the user to modify the state of the current instance when an error occurs or an exception is thrown. Reject supports passing in a parameter as the cause of failure or rejection
const reject = (reason) = > {
// Promise can only be modified in the wait state
if (this.status === STATUS.PENDING) {
// Change the state of the instance to failed
this.status = STATUS.REJECTED
// Accept the failure cause sent by the user
this.reason = reason
// After the state changes, the successful method set call then is initiated to implement asynchronous operation
this.onRejectedCallbacks.forEach((fn) = > fn())
}
}
try {
// New Promise() passes in a function that executes by default and takes two executable method arguments
executor(resolve, reject)
} catch (error) {
// If the default function fails, the state is set to reject
reject(error)
}
}
/** * 1. Each Promise prototype has a THEN method on it that provides two executable method arguments *@param {*} Ondepressing successfully performs the function *@param {*} 2. The then method must return a promise. We do this by new a new promise *@returns New Promise() * 3. Then if the current successful/failed method is called, if an exception is thrown, then the next failed method will be used * + return the success/failure value x * + if x is an object * * + x is a Promise that obtains the THEN of X and continues to invoke the THEN until it succeeds in obtaining the normal object or base data type * + Successful and failed methods invoke the Promise in the promise2 THEN Resolve /reject methods * + The success or failure of the next THEN is determined by the success or failure of X * + If x is the base data type, the success method of the next THEN will be called (success or failure of the then method will be called) * If the Promise returned by the callback fails, then the next call will be resolved. If the Promise returned by the callback fails, then the next call will be resolved
then(onFulfilled, onRejected) {
// If onFulfilled/onRejected cannot obtain the current success/failure value to the next THEN
// New promise((resolve,reject) =>{resolve(1)}).then().then().then(of=> {console.log(of)}) needs to be able to pass in the value of
onFulfilled =
typeof onFulfilled === 'function' ? onFulfilled : (data) = > data
onRejected =
typeof onRejected === 'function'
? onRejected
: (e) = > {
throw e
}
// Create a new promise2 and return it at the end of the method, then reaches the chain call, continuing then is actually calling the new promise instance
const promise2 = new Promise((resolve, reject) = > {
// When we call the then method, we call different pass-the-argument methods by determining the current Promise state
// When the promise state is Fulfilled, the onFulfilled method is called and the success value is passed in as the parameter
if (this.status === STATUS.FULFILLED) {
// We need to handle promise2 and x, but promise2 is assigned after new, so we need to use an asynchronous macro/micro to get the promise
setTimeout(() = > {
try {
const x = onFulfilled(this.value)
// We use an external method to unify the processing here
resolvePromise(x, promise2, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)}// When the promise status is REJECTED, call the onRejected method and pass the failure reason as an argument
if (this.status === STATUS.REJECTED) {
setTimeout(() = > {
try {
const x = onRejected(this.reason)
// We use an external method to unify the processing here
resolvePromise(x, promise2, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)}// When the state of a promise is PENDING, it is an asynchronous operation
// Publish the resolve and reject methods in publish-subscribe mode and invoke them when subsequent state changes are complete
if (this.status === STATUS.PENDING) {
// Use decorator pattern/slicing to wrap the success/failure methods so that we can do other operations and get value/ Reason after state changes
this.onFulfilledCallbacks.push(() = > {
setTimeout(() = > {
try {
const x = onFulfilled(this.value)
// We use an external method to unify the processing here
resolvePromise(x, promise2, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)})this.onRejectedCallbacks.push(() = > {
setTimeout(() = > {
try {
const x = onRejected(this.reason)
// We use an external method to unify the processing here
resolvePromise(x, promise2, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)})}})return promise2
}
}
// Handle subsequent state changes to the Promise(promise2) returned in then
function resolvePromise(x, promise2, resolve, reject) {
// If promise2 is passed in by itself
if (x === promise2) {
return reject(new TypeError('Repeat call'))}// Determine if the passed x is an object or method
if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
// In order to be compatible with other promises, add an anti-repetition feature to prevent other promises from being called repeatedly
let called
Reject (x) if an exception is thrown
try {
// Get the then attribute of x
const then = x.then
// If then is function, we default x to Promise
if (typeof then === 'function') {
// Call the then method of x, and call the corresponding success and failure if x succeeds/fails internally
then.call(
x,
(y) = > {
if (called) return
called = true
// If y is still a Promise, then needs to be resolved again to know that it is not a Promise
// resolve(y)
resolvePromise(y, promise2, resolve, reject)
},
(r) = > {
if (called) return
called = true
reject(r)
}
)
} else {
// If then is not a function, we default x to a non-promise object. Resolve (x)
resolve(x)
}
} catch (error) {
if (called) return
called = true
reject(error)
}
} else {
Resolve (x) if the value passed is a primitive value type
resolve(x)
}
}
Promise.defer = Promise.deferred = function () {
let dfd = {}
dfd.promise = new Promise((resolve, reject) = > {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
module.exports = Promise
Copy the code
The code address
code