This document has been updated on Github.
Promise queues asynchronous tasks, outputs multiple asynchronous tasks in sequence, and solves callback hell with chained calls.
The complete source code is attached at the end of the article
usage
var p = new Promise((resolve, reject) = > {
setTimeout(() = > {
resolve('I'm returning data asynchronously')},1000);
});
p.then(data= > {
console.log(data);
});
Copy the code
Promise specification
- There are three states: pending, fulfilled, and rejected.
- The initial state is wait state, which can be transformed into execution state and failure state
- Execution states cannot be converted to other states and must have an immutable final value.
- Failure states cannot be converted to other states, and there must be an immutable reason.
- You have to provide a
then
Method to access its current value, final value, and cause then
Method provides two parameters:onFulfilled
andonRejected
onFulfilled
andonRejected
If it is not a function type, it must be ignored- if
executor
Execution error, direct executionreject
- different
promise
You can apply it to each other
For more specification details, see the Promise A+ specification
The initial version of the specification
// Generate the initial version according to the specification
class NewPromise {
constructor (executor) {
// There are three states
this.enumStatus = {
PADDING: 'padding'.FULFILLED: 'fulfilled'.REJECTED: 'rejected'
};
// The initial state is waiting
this.state = this.enumStatus.PADDING;
// There must be an end value
this.value = ' ';
// There must be a reason for rejection
this.reason = ' ';
// Convert the execution state
let resolve = (value) = > {
// Modify the status
this.state = this.enumStatus.FULFILLED;
// Must have a final value
this.value = value;
};
// Change the failed state
let reject = (reason) = > {
// Modify the status
this.state = this.enumStatus.REJECTED;
// If the state is a failure, there must be a cause
this.reason = reason;
};
// Reject is rejected if an error occurs
try {
executor(resolve, reject);
} catch(err) { reject(err); }}// Must include the THEN method
then (onFulfilled, onRejected) {
This is very depressing. This is very depressing
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value;
onRejected = typeof onRejected === 'function' ? onRejected : err= > { Throw(err) };
If the current state is executed, the onFulfilled function will be executed with the final value as the parameter. Otherwise, it will be ignored
if (this.state === this.enumStatus.FULFILLED) {
onFulfilled(this.value);
}
// If the current state is rejected, the onRejected function will be executed, otherwise it will be ignored
if (this.state === this.enumStatus.REJECTED) {
onRejected(this.reason); }}}Copy the code
At this point the Promise has been able to achieve the basic function, do not believe the call to see.
var p = new NewPromise((resolve, reject) = > {
resolve('Tangerine on the front');
});
p.then((data) = > {
console.log(data);
});
// Output the front tangerine
Copy the code
But this does not solve the asynchronous problem.
var p = new NewPromise((resolve, reject) = > {
// The mock server returns data
setTimeout(() = > {
resolve('Tangerine on the front');
}, 1000);
});
p.then((data) = > {
console.log(data);
});
// Won't print anything
Copy the code
How to solve this problem? We can handle this with a publish-subscriber model.
Regardless of what happens in the THEN function during the time the server returns the data, why not use a queue to store the operations in the THEN in order to execute them after the server returns the data?
Resolve promise asynchrony
Only parts of the code will be modified, and no more duplicates will be mentioned
class NewPromise {
constructor (executor) {...// Task center
this.onFulfilledQueue = [];
this.onRejectedQueue = [];
// Convert the execution state
let resolve = (value) = >{...// When the server data returns, the prestored successful transactions are executed in turn
this.onFulfilledQueue.map(fn= > fn(this.value));
};
// Change the failed state
let reject = (reason) = >{...// When the server data returns, execute the prestored failed transactions in turn
this.onRejectedQueue.map(fn= > fn(this.reason)); }; . } then (onFulfilled, onRejected) { ...// If the current state is waiting, add it to the transaction
if (this.state === this.enumStatus.PADDING) {
this.onFulfilledQueue.push(() = > {
onFulfilled(this.value);
});
this.onRejectedQueue.push(() = > {
onRejected(this.reason); }); }}}Copy the code
Repeat the above operation, and it will definitely output what you want.
Two more questions:
- 1, since the chain call, it returns a
Promise
Object, new as a result. - 2, chained calls, if the user returns itself as a result, will cause an infinite loop, so we need to do some comparison processing
- 3. Others (refer to Promise A+ specification for details)
The solution
- if
promise
andx
If yes, the execution is rejectedpromise
- if
x
Is an object or function that hasthen
Property is a function, thenx
Execute as a scopethen
function - if
then
The value returned after execution is still onepromise
Is called recursivelyresolvePromise
- Or otherwise
x
Execute for parameterspromise
- if
x
Is not an object or function, thenx
Execute for parameterspromise
/ * * *@param Promise2 New promise *@param X Original promise *@param Resolve Resolve * of promise2@param Reject Reject */ of promise2
function resolvePromise(promise2, x, resolve, reject) {
// Prevent looping calls
if (promise2 === x) {
reject(Throw('Circular calls not allowed'));
}
if(x ! =null && (typeof x === 'object' || typeof x === 'function')) {
// Function or object
let then = x.then;
if (typeof then === 'function') {
// If the return value of the function is still a promise, then call resolvePromise recursively
then.call(x, res= > resolvePromise(promise2, res, resolve, reject), err= > reject(err));
}else{ resolve(x); }}else {
/ / common values
resolve(x)
}
}
Copy the code
Modified THEN method
With the solution passed, we then use the resolvePromise in the THEN method
class NewPromise {... then (onFulfilled, onRejected) {This is very depressing. This is very depressing
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value;
onRejected = typeof onRejected === 'function' ? onRejected : err= > { Throw(err) };
let promise2 = new NewPromise((resolve, reject) = > {
If onFulfilled is a function type and the state is executed, the function will be executed with the final value as the parameter of the function. Otherwise, it will be ignored
if (this.state === this.enumStatus.FULFILLED) {
setTimeout(() = > {
resolvePromise(promise2, onFulfilled(this.value), resolve, reject);
}, 0);
}
// If onRejected is the function type and the state is rejected, then the function is executed with the evidence as its argument, otherwise it is ignored
if (this.state === this.enumStatus.REJECTED) {
setTimeout(() = > {
resolvePromise(promise2, onRejected(this.reason), resolve, reject);
}, 0);
}
// If the current state is waiting, add it to the transaction
if (this.state === this.enumStatus.PADDING) {
this.onFulfilledQueue.push(() = > {
resolvePromise(promise2, onFulfilled(this.value), resolve, reject);
});
this.onRejectedQueue.push(() = > {
resolvePromise(promise2, onRejected(this.reason), resolve, reject); }); }});returnpromise2; }}Copy the code
The then method is replaced by a resolvePromise. Many people will say why there is a setTimeout?
If you look closely at the specification, there is a clause in which the then method can be called multiple times by the same promise.
The promise returned is an asynchronous object. If you do not use setTimeout, it is possible to execute only the first THEN and not the next.
Try calling
var p1 = new NewPromise((resolve, reject) = > {
setTimeout(() = > {
resolve('Tangerine on the front');
}, 3000);
});
var p2 = new NewPromise((resolve, reject) = > {
setTimeout(() = > {
resolve('I'm the result of a chained call')},3000);
})
p1.then((data) = > {
console.log(data);
return p2;
}).then(data= > {
console.log(data);
});
Copy the code
At this point, the promise was truly complete.
This is not a complete code because there are several areas in the case that are not designed, such as promise.all, promise.resolve, promise.reject, promise.race, so this article is for reference only
Complete code of case
// Generate the initial version according to the specification
class NewPromise {
constructor (executor) {
// There are three states
this.enumStatus = {
PADDING: 'padding'.FULFILLED: 'fulfilled'.REJECTED: 'rejected'
};
// The initial state is waiting
this.state = this.enumStatus.PADDING;
// There must be an end value
this.value = ' ';
// There must be a reason for rejection
this.reason = ' ';
// Task center
this.onFulfilledQueue = [];
this.onRejectedQueue = [];
// Convert the execution state
let resolve = (value) = > {
// Modify the status
this.state = this.enumStatus.FULFILLED;
// Must have a final value
this.value = value;
// When the server data returns, the prestored successful transactions are executed in turn
this.onFulfilledQueue.map(fn= > fn(this.value));
};
// Change the failed state
let reject = (reason) = > {
// Modify the status
this.state = this.enumStatus.REJECTED;
// If the state is a failure, there must be a cause
this.reason = reason;
// When the server data returns, execute the prestored failed transactions in turn
this.onRejectedQueue.map(fn= > fn(this.reason));
};
// Reject is rejected if an error occurs
try {
executor(resolve, reject);
} catch(err) { reject(err); }}// Must include the THEN method
then (onFulfilled, onRejected) {
This is very depressing. This is very depressing
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value;
onRejected = typeof onRejected === 'function' ? onRejected : err= > { Throw(err) };
let promise2 = new NewPromise((resolve, reject) = > {
If onFulfilled is a function type and the state is executed, the function will be executed with the final value as the parameter of the function. Otherwise, it will be ignored
if (this.state === this.enumStatus.FULFILLED) {
setTimeout(() = > {
resolvePromise(promise2, onFulfilled(this.value), resolve, reject);
}, 0);
}
// If onRejected is the function type and the state is rejected, then the function is executed with the evidence as its argument, otherwise it is ignored
if (this.state === this.enumStatus.REJECTED) {
setTimeout(() = > {
resolvePromise(promise2, onRejected(this.reason), resolve, reject);
}, 0);
}
// If the current state is waiting, add it to the transaction
if (this.state === this.enumStatus.PADDING) {
this.onFulfilledQueue.push(() = > {
resolvePromise(promise2, onFulfilled(this.value), resolve, reject);
});
this.onRejectedQueue.push(() = > {
resolvePromise(promise2, onRejected(this.reason), resolve, reject); }); }});returnpromise2; }}function resolvePromise(promise2, x, resolve, reject) {
// Prevent looping calls
if (promise2 === x) {
reject(Throw('Circular calls not allowed'));
}
if(x ! =null && (typeof x === 'object' || typeof x === 'function')) {
// Function or object
let then = x.then;
if (typeof then === 'function') {
// If the return value of the function is still a promise, then call resolvePromise recursively
then.call(x, res= > resolvePromise(promise2, res, resolve, reject), err= > reject(err));
}else{ resolve(x); }}else {
/ / common values
resolve(x)
}
}
/ / call
var p1 = new NewPromise((resolve, reject) = > {
setTimeout(() = > {
resolve('Tangerine on the front');
}, 1000);
});
var p2 = new NewPromise((resolve, reject) = > {
setTimeout(() = > {
resolve('I'm the result of a chained call')},3000);
})
p1.then((data) = > {
console.log(data);
return p2;
}).then(data= > {
console.log(data);
});
Copy the code
For more documentation, see:
Online address [front Tangerine gentleman]
GitHub Warehouse [Front Tangerine]