Turn:
Handwritten Promise reading one is enough
catalogue
- outline
- Blog ideas
- API features and handwritten source code
- Constructor
- then
- catch
- Promise.resolved
- Promise.rejected
- Promise.all
- Promise.race
outline
This article mainly introduces the features of the api defined on Promise and how handwriting implements these features. The purpose is to output the output in the learning process in the form of blog, consolidate knowledge and facilitate later review
Blog ideas
Search Promise on mdn to understand the definitions of classes and APIs:
- What attributes are defined and what meanings they represent
- What parameters the api needs to pass, what values it returns, and what exceptions it may throw
- Look at the official use cases and guess the possible internal implementation
- Write the source code and verify with the official use case to see whether the return value is consistent
API features and handwritten source code
Constructor
- promise has states pending, rejected and resolved, so there should be a variable to save the state
- The constructor parameter excutor is a callback function executed synchronously. The parameters executed by the function are two functions resolved and rejected. Therefore, two functions need to be defined inside promise and passed in where excutor is executed in the number of construction lines
- The callback functions onResolved and onRejected will be passed in. Then, and the corresponding callback functions will be triggered in resolved and rejected respectively, so two arrays are needed to save the callbacks passed in then
- resolved and rejected can only be executed once. After execution, the state of promise will change, and the parameters will be passed to the callback function
- onRejected and onResolved execute asynchronously
- Exception throwing by excutor will directly execute rejected, so catch error is required for excutor execution
const PENDING = "PENDING"; const RESOLVED = "resolved"; const REJECTED = "rejected"; function MyPromise(excutor){ // promise stores the status value internally this.status = PENDING; this.data = null; // The callback function passed in from the then method is saved here this.onResolvedList = []; this.onRejectedList = []; let resolved = (value) => { // The resolved function can only be executed once, so judge whether the status is pending first if(this.status !== PENDING){ return; } // The change status is resolved this.status = RESOLVED; // The data is the value passed in this.data = value; // Judge whether there is an onResolved callback that has been penetrated. If so, it will be executed asynchronously if(this.onResolvedList.length > 0){ setTimeout(() => { this.onResolvedList.forEach(onResolved => { onResolved(value); }); }, 0); } } let rejected = (reason) => { if(this.status !== PENDING){ return } this.status = REJECTED; this.data = reason; if(this.onRejectedList.length > 0){ setTimeout(() => { this.onRejectedList.forEach(onRejected => { onRejected(reason); }); }); } } try{ // The executor functions are executed synchronously, and the parameters are resolve and rejected defined in promise excutor(resolved, rejected); }catch(error){ // If the executor function fails, execute rejected directly this.rejected(error); } }
then
- then will accept two callback functions onResolved and onRejected
- onResolved and onRejected are called asynchronously
- then returns a new promise object
- If the parameters of then are not passed, then value and reason continue to be passed down
- If the promise state is still pending when then is executed, only the callback is saved, and ensure that the state of the new promise can be modified after the callback is executed
- If the triggered callback function throws an exception, the status of the returned new callback function is rejected, and reason will catch the error
- If the return value of the triggered corresponding callback function is not a promise object, the return state of the new promise is resolved, and value is the return value of the callback passed in to then
- If the return value of the triggered corresponding callback is a promise object, the status of the new promise return value depends on the promise returned by the modified callback
MyPromise.prototype.then = function(onResolved, onRejected){ // If onResolved is not passed, set onResolved as the function that returns value onResolved = typeof onResolved === "function" ? onResolved : value => value // If onRejected is not passed, set onRejected to the function of reason at the disposal onRejected = typeof onRejected === "function" ? onRejected : reason => {throw reason} return new MyPromise((resolved, rejected) => { // Pass in the callback function to execute let callBackExcu = (callback) => { try{ let result = callback(this.data); if(result instanceof MyPromise){ // If the return value of the callback is still promise, the state of the promise returned by then depends on the promise returned by the callback. Resolve will be executed if successful, and reject will be executed if failed result.then(resolved, rejected); }else{ // If the return value of the callback is not promise, the new promise status is resolved resolved(result) } }catch(error){ // If the callback execution throws an exception, the new promise status is rejected rejected(error); } } if(this.status === PENDING){ // If the status is pending, save the callback and ensure that the status of the currently returned promise can be modified after the callback is executed this.onResolvedList.push((value) => { callBackExcu(onResolved) }); this.onRejectedList.push((reason) => { callBackExcu(onRejected) }); }else{ // If the status is not pending, the corresponding callback is executed according to the status, and the status of the current promise is modified switch(this.status){ case REJECTED: // onRejected asynchronous execution setTimeout(() => { callBackExcu(onRejected); }); break; case RESOLVED: // Asynchronous resolve setTimeout(() => { callBackExcu(onResolved); }); break; } } }); }
catch
catch and then are almost the same. The difference is that the only parameter passed in is onRejected, so
MyPromise.prototype.catch = function(onRejected){ // The difference between catch and then is that the parameters passed in are different. There is no need to pass onResolve return this.then(null, onRejected); }
Promise.resolved
- resolved returns a promise object
- If the parameter passed in is a primise object, it will be returned directly
- If it is an object containing the "then" method, a new promise object is returned, and the state depends on the execution of the then function. If an error is thrown in the execution of then, the new promise state is rejected
- The parameters of then are the two callback functions resolved and rejected
- If the passed in parameter value is neither an instance of promise nor an object with the then function, a new promise object is returned and the object data is changed to value
MyPromise.resolve = function(value){ if(value instanceof MyPromise){ // If the parameter passed in is a primise object, it will be returned directly return value; }else if(typeof value.then === "function"){ return new MyPromise((resolved, rejected) => { try{ // The parameters of then are the two callback functions resolved and rejected value.then(resolved, rejected); }catch(error){ // If an error is thrown during the execution of then, the new promise status is rejected rejected(error); } }); }else{ // If the passed in parameter value is neither an instance of promise return new MyPromise((resolved, rejected) => { resolved(value); }); } }
Promise.rejected
- Accept the parameter reason and return a promise instance with the status of rejected and the data of reason
MyPromise.reject = function(reason){ return new MyPromise((resolved, rejected) => { rejected(reason); }); }
Promise.all
- The received parameters need to meet the iterative protocol, otherwise errors will be thrown
- The return value is promise
- If the passed in parameter is an empty and iteratable object, a promise instance with the status of resolved is returned, and the data is an empty array,
Promise.all([]) // Promise { : Array(0)} Promise.all("") // Promise { : Array(0)}
- If there is no promise instance in the passed in parameter, or all promises are already in resolved status, a promise with pending status will be returned and asynchronously updated to resolved
let p = Promise.all([1,2,3,4,Promise.resolve(5)]) console.log(p); // Promise { }
- If there is a promise and the status is still pending, a promise instance will be returned. After all promises have been resolved, the status will be updated to resolved and the data will be in the order of incoming
Next, look at the source code
// First, define a method to verify whether the parameters meet the iterative protocol const isIterable = function(object){ return typeof object[Symbol.iterator] === "function" && typeof object[Symbol.iterator]() === "object" && typeof object[Symbol.iterator]().next === "function" } MyPromise.all = function(iterable){ if(!isIterable(iterable)){ // Fail to satisfy iterative protocol throw error throw new TypeError("Object is not iterable"); } let data = []; let count = 0; // Iterative parameter generation array let params = Array.from(iterable); return new MyPromise((resolved, rejected) => { if(params.length === 0){ // If it is an empty iteratable object, an empty array is returned resolved(data); }else{ params.forEach((element, index) => { // Traverse each parameter and uniformly process it into an instance of promise // That's one less branch of logic let itemPromise = MyPromise.resolve(element); itemPromise.then( value => { // The results in data should be in the same order as the parameters passed in data[index] = value; if(count === params.length - 1){ // That means it's all resolved resolved(data); } count++; }, reason => { // reject returns directly rejected(reason); } ); }); } }); }
Promise.race
- Receive an iteratable object, which is the same as the method "all"
- Returns a new promise
- The returned promise status is pending and asynchronously updated to resolved
let p = Promise.race([1,2,3,4]); console.log(p); // Promise { } p.then( value => { console.log(value); // Promise{ : 1} } );
- If one of the incoming promises is resolved or rejected first, the returned promise status will be updated to resolved
let p1 = new Promise((resolved, rejected) => { setTimeout(() => { resolved("p1"); }, 10); }); let p2 = new Promise((resolved, rejected) => { setTimeout(() => { resolved("p2"); }, 100); }); let p = Promise.race([p2, p1]) p.then( value => { console.log(value); // p1 } );
Finally, let's take a look at the implementation of our source code
MyPromise.race = function(iterable){ if(!isIterable(iterable)){ // Fail to satisfy iterative protocol throw error throw new TypeError("Object is not iterable"); } const params = Array.from(iterable); return new MyPromise((resolved, rejected) => { params.forEach((element, index) => { const itemPromise = MyPromise.resolve(element); itemPromise.then( value => { // As long as there is a promise resolved, it returns directly resolved(value); }, error => { // As long as there is a promise rejected, it will be returned directly rejected(error); } ); }); }); }
Turn:
Handwritten Promise reading one is enough
--Posted from Rpc