Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.
-
Promises/A+
-
Promise A+ Chinese translation
The use of Promise often comes up in interviews; Some interviewers dig a little deeper and ask if they know how Promise is implemented, or if they’ve read the Promise source code. Today we’ll take a look at how Promise is implemented internally to make chained calls.
What is a Promise
Before you write a generic Promise, know what it is:
- Promise is a class
- Each time a new Promise is passed an executor, the executor is executed immediately (as soon as the new Promise is executed)
- The executor function takes two arguments, resolve,reject
- Pendding => resolve Reject indicates failure
- If once successful can not change into failure; Once you fail, you can’t succeed; You can change the state only if the current state is Pendding
- Each Promise has a then method
/ /! 3. Default Promise three states: Pendding,fulfiled,reject
const PENDDING = 'pendding'; / / wait for
const FULFILLED = 'fulfilled'; / / success
const REJECT = 'reject'; / / fail
class Promise {
constructor(executor) {
this.value = undefined; // Success message
this.reason = undefined; // Failed message
this.status = PENDDING; / / state values
/ /! 2. There are two parameters in the actuator: resolve and reject
let resolve = (value) = > {
/ /! 4. You can change the status only when the current status is Pendding
if (this.status === PENDDING) {
this.value = value;
this.status = FULFILLED
}
}
let reject = (reason) = > {
if (this.status === PENDDING) {
this.reason = reason;
this.status = REJECT
}
}
//* An exception may occur (throw new Error)
try {
/ /! 1. Create a Promise executor that will be executed immediately
executor(resolve, reject);
} catch (e) {
reject(e)
}
}
/ /! 5. Each Promise has a THEN method, which determines the current state and executes the corresponding method
then(onFulfilled, onReject) {
// this is a big pity. * Then there is two ways, onpity and onReject.
if (this.status === FULFILLED) {
onFulfilled(this.value)
}
if (this.status === REJECT) {
onReject(this.reason)
}
if (this.status === PENDDING) {
}
}
}
// Export the current class commonJS definition
module.exports = Promise
Copy the code
Use:
let Promise = require('./promise')
let p = new Promise((resolve, reject) = > {
resolve('I have money')
// reject(' I have no money ')
// throw new Error(' failed '); // If an exception is thrown, execution fails
})
// The callback problem is not completely resolved
p.then(data= > { // Successful callback
console.log('success' + data);
}, err= > { // Failed callback
console.log('err' + err);
})
Copy the code
Asynchronous invocation of Promise
- The Promise class at this point is a synchronous execution function that cannot be executed at one step, and according to the specification can execute multiple THEN methods within a Promise.
let Promise = require('./promise')
let p = new Promise((resolve, reject) = > {
setTimeout(() = > {
resolve('I have money')
// reject(' I have no money ')
// throw new Error(' failed '); // If an exception is thrown, execution fails
}, 1000);
})
// The callback problem is not completely resolved
p.then(data= > { // Successful callback
console.log('success' + data);
}, err= > { // Failed callback
console.log('err' + err);
})
p.then(data= > { // Successful callback
console.log('success' + data);
}, err= > { // Failed callback
console.log('err' + err);
})
p.then(data= > { // Successful callback
console.log('success' + data);
}, err= > { // Failed callback
console.log('err' + err);
})
Copy the code
Execution result: There is no execution result
This. OnResolvedCallbacks = []; setTimeout is executed asynchronously, the Premise is in pendding state, and the result cannot be returned. Storage space, and when the result is failure this.onRejectCallbacks = []; Store the execution functions (subscriptions) in the then method multiple times, and execute the subscription method immediately when the asynchronous function executes, that is, when the publisher publishes.
/ /! 3. Default Promise three states: Pendding,fulfiled,reject
const PENDDING = 'pendding'; / / wait for
const FULFILLED = 'fulfilled'; / / success
const REJECT = 'reject'; / / fail
class Promise {
constructor(executor) {
this.value = undefined; // Success message
this.reason = undefined; // Failed message
this.status = PENDDING; / / state values
/ /! 6. A promise can be executed multiple times then(asynchronous execution, equivalent to publish-subscribe)
this.onResolvedCallbacks = [];
this.onRejectCallbacks = [];
/ /! 2. There are two parameters in the actuator: resolve and reject
let resolve = (value) = > {
/ /! 4. You can change the status only when the current status is Pendding
if (this.status === PENDDING) {
this.value = value;
this.status = FULFILLED
this.onResolvedCallbacks.forEach(fn= > fn()) Resolve is executed after then, and if it succeeds, execute these functions in turn}}let reject = (reason) = > {
if (this.status === PENDDING) {
this.reason = reason;
this.status = REJECT
this.onRejectCallbacks.forEach(fn= > fn())
}
}
//* An exception may occur (throw new Error)
try {
/ /! 1. Create a Promise executor that will be executed immediately
executor(resolve, reject);
} catch (e) {
reject(e)
}
}
/ /! 5. Each Promise has a THEN method, which determines the current state and executes the corresponding method
then(onFulfilled, onReject) {
// this is a big pity. * Then there is two ways, onpity and onReject.
if (this.status === FULFILLED) {
onFulfilled(this.value)
}
if (this.status === REJECT) {
onReject(this.reason)
}
//* Execute publish subscribe asynchronously when the state is considered to be in wait state
if (this.status === PENDDING) {
this.onResolvedCallbacks.push(() = > { / / * subscription
//* The arrow function is used because there are other things you can do in this function
// todo...
onFulfilled(this.value)
})
this.onRejectCallbacks.push(() = > {
onReject(this.reason)
})
}
}
}
// Export the current class commonJS definition
module.exports = Promise
Copy the code
The chain call to Promise
- The principle of
- If a normal value is returned, the next THEN succeeds
- If an error is thrown then the method fails
- If it’s a PROMISE, let the promise implementation take its state
- A new Promise is returned to implement the chain call
- Node reads multiple files, and the native methods are controlled by the first argument of the function
let fs = require('fs');
fs.readFile('./name.txt'.'utf8'.(err,data) = > {
if(err) {
return console.log(err);
}
fs.readFile(data,'utf8'.(err,data) = > {
if(err) {
return console.log(err);
}
console.log(data); })})Copy the code
- Into a promise
If you need to make a promise, make the callback method a promise
function readFile(. args) {
return new Promise((resolve, reject) = >{ fs.readFile(... args,function (err, data) {
if (err) reject(err)
resolve(data)
})
})
}
// chain call
readFile('./name.txt'.'utf8').then(data= > {
return readFile(data,'utf8')
}).then(data= > {
console.log(data);
},err= > {
console.log(err);
})
Copy the code
- Handwriting realizes its principle
- If a normal value is returned, the next THEN succeeds
- If an error is thrown then the method fails
- If it’s a PROMISE, let the promise implementation take its state
- A new Promise is returned to implement the chain call
Continue with step 6 above
/ /! 3. Default Promise three states: Pendding,fulfiled,reject
const PENDDING = 'pendding'; / / wait for
const FULFILLED = 'fulfilled'; / / success
const REJECT = 'reject'; / / fail
//* Promise handler
/ /! 10. X could be a normal value, it could be a promise
const resolvePromise = (promise2, x, resolve, reject) = > {
//* Processes the type of x to decide whether to call resolve or reject
/ /! 12 wait for yourself, will enter a loop, error, judgment
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))}/ /! 13. To determine if x is a normal value, consider yourself a promise
if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
/ /! 14. How to judge whether a promise is a promise by then
try { / /! Objec. DefineProperty (); Objec. DefineProperty ()
let then = x.then; / /! 15. See if there are any then methods
let called; / /! 18. By default, there is no call success or failure. If there is a call, it returns to prevent multiple calls
//* check if then is a method
if(typeof then === 'function') { // {then:function(){}}
/ / is a promise
// x.chen (()=>{},()=>{}
then.call(x,y= > { // If it is a promise, use the result of that promise
if(called) return
called = true;
/ /! 17. Y may also be a promise to implement recursive resolution
resolvePromise(promise2,y,resolve,reject)
},r= >{
if(called) return
called = true;
reject(r)
})
}else {
resolve(x)// Just throw the constant}}catch (e) {
if(called) return
called = true;
reject(e); // Then throws an exception}}else {
resolve(x) / /! 13. Either promise or normal value, return directly}}class Promise {
constructor(executor) {
this.value = undefined; // Success message
this.reason = undefined; // Failed message
this.status = PENDDING; / / state values
/ /! 6. A promise can be executed multiple times then(asynchronous execution, equivalent to publish-subscribe)
this.onResolvedCallbacks = [];
this.onRejectCallbacks = [];
/ /! 2. There are two parameters in the actuator: resolve and reject
let resolve = (value) = > {
/ /! 4. You can change the status only when the current status is Pendding
if (this.status === PENDDING) {
this.value = value;
this.status = FULFILLED
this.onResolvedCallbacks.forEach(fn= > fn()) Resolve is executed after then, and if it succeeds, execute these functions in turn}}let reject = (reason) = > {
if (this.status === PENDDING) {
this.reason = reason;
this.status = REJECT
this.onRejectCallbacks.forEach(fn= > fn())
}
}
//* An exception may occur (throw new Error)
try {
/ /! 1. Create a Promise executor that will be executed immediately
executor(resolve, reject);
} catch (e) {
reject(e)
}
}
/ /! 5. Each Promise has a THEN method, which determines the current state and executes the corresponding method
then(onFulfilled, onReject) {
// this is a big pity. * Then there is two ways, onpity and onReject.
/ /! 19. Optional parameter. If there is no ondepressing,onReject, you can give a default parameter
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val= >val;
onReject = typeof onReject === 'function' ? onReject : err= >{throw err};
/ /! 7. The then method should return a new promise for successive calls
//* Execute the new Promise and then return promise2. Promise2 is undefined and a timer is required
let promise2 = new Promise((resolve, reject) = > {
/ /! 8. The last state should be retrieved in the returned promise to determine whether the promise2 succeeds or fails
if (this.status === FULFILLED) {
/ /! 9. Catch exceptions (if a chain call throws new Error('err'))
/ /! This is a big onFulfilled,onRejected cannot be implemented in the current context, and promise2 needs to be asynchronous to ensure the existence of promise2
setTimeout(() = > {
try {
let x = onFulfilled(this.value);
/ /! 10. Handle x values externally
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
})
}
if (this.status === REJECT) {
setTimeout(() = > {
try {
let x = onReject(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
if (this.status === PENDDING) {
this.onResolvedCallbacks.push(() = > { / / * subscription
//* The arrow function is used because there are other things you can do in this function
// todo...
setTimeout(() = > {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
this.onRejectCallbacks.push(() = > {
setTimeout(() = > {
try {
let x = onReject(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
})
return promise2
}
}
// Export the current class commonJS definition
module.exports = Promise
Copy the code
use
let Promise = require('./promise')
1. If a normal value is returned, the success of the next then * 2. If an error is thrown, then the method fails * 3. If it is a Promise, let the promise implement adopt its state * 4. A new Promise is returned to implement the chain call */
const p = new Promise((resolve,reject) = >{
// resolve(new Promise((resolve,reject)=>{
setTimeout(() = >{
resolve('hello')
// reject('hello')
},1000)
// }))
})
// let obj = {}
// Object.defineProperty(obj,'then', {
// get() {
// throw new Error(' failed ')
/ /}
// })
let promise2 = p.then(data= > {
return new Promise((resolve,reject) = > {
setTimeout(() = > {
resolve('222')},1000);
})
})
promise2.then(data= > {
console.log(data);
},err= > {
console.log(err);
})
Copy the code
Test written promises to see if they comply with the specification
Install the test plug-in globally
npm i promises-aplus-tests -g
Copy the code
Add the following code to the code you want to test:
/ /! 20. Test that promises comply with the specification
Promise.deferred = function(){
let dfd = {};
dfd.promise = new Promise((resolve,reject) = >{
dfd.resolve = resolve;
dfd.reject = reject
})
return dfd;
}
Copy the code
Start testing:
promises-aplus-tests promise.js
Copy the code