You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In this post we will be trying to build a simple MyPromise class in javascript which imitates the standard Promise class. We will implement an executor, add asynchronous logic, and error handling step by step to our custom MyPromise class.
Basic Structure
Let us start from the most basic requirements of the Promise class:
while initializing the Promise class, one needs to pass in an executor, which is executed immediately(the executor function per se is executed but it might contains asynchronous process as we will see soon) ;
a promise can have three status: FULFILLED, REJECTED and PENDING. Its default status is FULFILLED and once the status is set to either REJECTED or PENDING, the status can no longer be modified;
the reject function is used to set the status to REJECTED, while the resolve function is used to set status to fulfilled;
One can create a new Promise instance in the following way:
, and it shall be executed immediately which means that it should be called directly in the constructor of the class. Let us first set up the skeleton code for our MyPromise class:
constPENDING="PENDING";constFULFILLED="fulfilled";constREJECTED="REJECTED";classMyPromise{constructor(executor){executor(this.resolve,this.reject);}status=PENDING;resolve=()=>{// status cannot be changed if it is already fulfilled or REJECTEDif(this.status===PENDING){this.status=FULFILLED;}else{return;}}reject=()=>{// status cannot be changed if it is already fulfilled or REJECTEDif(this.status===PENDING){this.status=REJECTED;}else{return;}}}module.exports=MyPromise;
The then method
The next step is to create the then method for our *MyPromise8 class. Recall that the then method accepts two parameters, a success callback function called when the status of the instance is fulfilled, and a failure callback.
There is one thing that is still missing here, in our very first usage example above, you can see that both resolve and reject will accept one parameter. Also resolve should pass that parameter to the successCallback, while reject should pass it to the failureCallback. We thus modify our code as follows, storing two extra variables in the class.
constPENDING="PENDING";constFULFILLED="fulfilled";constREJECTED="REJECTED";classMyPromise{constructor(executor){executor(this.resolve,this.reject);}status=PENDING;//sotring value here and pass it to the successCallbackvalue=undefined;reason=undefined;resolve=(value)=>{if(this.status===PENDING){this.status=FULFILLED;}else{return;}this.value=value;}reject=(reason)=>{if(this.status===PENDING){this.status=REJECTED;}else{return;}this.reason=reason;}then=(successCallback,failureCallback)=>{if(this.status===FULFILLED){successCallback(this.value);}elseif(this.status===REJECTED){failureCallback(this.reason);}}}
Asynchronous MyPromise
We want to modify our class so that it can handle asynchronous action in the executor. If we run the following example with our current set up, it will not log anything, because when the then method is called, the status is till PENDING.
All we need to do then is to store the callbacks in the class when status === PENDING, and call them when the executor is done executing.
status=PENDING;value=undefined;reason=undefined;//sotring the callbacks heresuccessCallBack=undefined;failureCallBack=undefined;resolve=(value)=>{if(this.status===PENDING){this.status=FULFILLED;}else{return;}this.value=value;//call successcallback when resolve done executingthis.successCallback&&this.successCallback(this.value);}reject=(reason)=>{if(this.status===PENDING){this.status=REJECTED;}else{return;}this.reason=reason;//call failurecallback when reject is done executingthis.failureCallback&&this.failureCallback(this.reason);}then=(successCallback,failureCallback)=>{if(status===FULFILLED){successCallback(this.value);}elseif(status===REJECTED){failureCallback(this.reason);}else{//if the status is still PENDING, store the callbacksthis.successCallback=successCallback;this.failureCallback=failureCallback;}}
Calling then multiple times
We want to be able to call the then method multiple times:
. Our implementation currently cannot handle this situation because the second call to then will override the callbacks stored by the first then and etcetera. The only change we need to make to accommodate this is to store all callbacks in an array.
status=PENDING;value=undefined;reason=undefined;//sotre all callbacks in an arraysuccessCallBack=[];failureCallBack=[];resolve=(value)=>{if(this.status===PENDING){this.status=FULFILLED;}else{return;}this.value=value;while(this.successCallback.length){//the shift function will pop up the first element in the array(this.successCallback.shift())(this.value);}}reject=(reason)=>{if(this.status===PENDING){this.status=REJECTED;}else{return;}this.reason=reason;while(this.failureCallback.length){(this.failureCallback.shift())(this.reason)}}then=(successCallback,failureCallback)=>{if(status===FULFILLED){successCallback(this.value);}elseif(status===REJECTED){failureCallback(this.reason);}else{//if the status is still PENDING, store the callbacksthis.successCallback.push(successCallback);this.failureCallback.push(failureCallback);}}
Chaining then methods
Now we want to be able to chain multiple calls to the then method:
Furthermore The second value appearing above in then should be the return value of the first then while the first value is the value passed in by resolve as usual. We have to return a MyPromise object from the then method if we want to chain the then's together.
then=(successCallback,failureCallback)=>{//wrap all the actions here in the exectuor of the next MyPromise instance so that they//will get executed instantlyletpromise2=newMyPromise((resolve,reject)=>{if(this.status===FULFILLED){//if the first then calls its successCallback,//resolve will pass its return value to the next //MyPromise instance's successCallbackletx=successCallback(this.value);resolve(x);}elseif(this.status===REJECTED){x=failureCallback(this.reason);}else{this.successCallback.push(successCallback);this.failureCallback.push(failureCallback);}})returnpromise2;}
We have only dealt with the situation when the status of the first Promise in the chain is FULFILLED, and we will take care of the failure and asynchronous mode together later.
Return a MyPromise object and handle chaining cycle
For now we can only return a value in then, but the actual Promise also allows one to return a Promise object.
then=(successCallback,failureCallback)=>{letpromise2=newMyPromise((resolve,reject)=>{if(this.status===FULFILLED){letx=successCallback(this.value);resolvePromise(x,resolve,reject);}elseif(this.status===REJECTED){x=failureCallback(this.reason);}else{this.successCallback.push(successCallback);this.failureCallback.push(failureCallback);}})returnpromise2;}resolvePromise(x,resolve,reject){//if the previous callback returns a MyPromise object//then we call resolve/reject depending on its statusif(xinstanceofMyPromise){x.then(resolve,reject);}else{resolve(x);}}
After making this improvement, we can now return a MyPromise object in our call:
This is called a chaining cycle(if you chase through the code, it is not hard to find that this will cause an infinite "cycle") by the standard Promise package, and we need to throw an exception if we find ourselves doing something like this.
then=(successCallback,failureCallback)=>{letpromise2=newMyPromise((resolve,reject)=>{if(this.status===FULFILLED){//here we need to use setTimeout, because we need promise2 to be created firstsetTimeout(()=>{letx=successCallback(this.value);resolvePromise(promise2,x,resolve,reject);},0)}elseif(this.status===REJECTED){x=failureCallback(this.reason);}else{this.successCallback.push(successCallback);this.failureCallback.push(failureCallback);}})returnpromise2;}functionresolvePromise(promise2,x,resolve,reject){if(x===promise2){returnreject(thrownewTypeError("chaining cycle detected for Mypromise #<MyPromise>"));}//if the previous callback returns a MyPromise object//then we call resolve/reject dePENDING on its statusif(xinstanceofMyPromise){x.then(resolve,reject);}else{resolve(x);}}
Here we are passing the new MyPromise to resolvePromise for it to check whether there is a chaining cycle. However, we are in the middle of creating the new MyPromise while doing this. In order to resolve this, we have to make resolvePromise asynchronous by invoking setTimeout with 0 delay.
Make the callback parameter of Then optional
then=(successCallback,failureCallback)=>{//manually create a callback if it is not specified successCallback=successCallback ? successCallback : value=>value;failureCallback=failureCallback ? failureCallback: reason=>{throwreason};letpromise2=newMyPromise((resolve,reject)=>{if(this.status===FULFILLED){//here we need to use setTimeout, because we need promise2 to be created firstsetTimeout(()=>{letx=successCallback(this.value);resolvePromise(promise2,x,resolve,reject);},0)}elseif(this.status===REJECTED){x=failureCallback(this.reason);}else{this.successCallback.push(successCallback);this.failureCallback.push(failureCallback);}})returnpromise2;}
Error Handling
Now we want to add error handling to MyPromise. To be more specific, we need to throw an exception when executor and resolvePromise encounters any. We also apply the same logic as before when we deal with the FULFILLED mode to the REJECTED and asynchronous mode.
In this post we will be trying to build a simple MyPromise class in javascript which imitates the standard Promise class. We will implement an executor, add asynchronous logic, and error handling step by step to our custom MyPromise class.
Basic Structure
Let us start from the most basic requirements of the Promise class:
One can create a new Promise instance in the following way:
, where myPromise will be our custom Promise class. The executor in this case is:
, and it shall be executed immediately which means that it should be called directly in the constructor of the class. Let us first set up the skeleton code for our MyPromise class:
The then method
The next step is to create the then method for our *MyPromise8 class. Recall that the then method accepts two parameters, a success callback function called when the status of the instance is fulfilled, and a failure callback.
In order to achieve that, we add the following code to our class definition:
There is one thing that is still missing here, in our very first usage example above, you can see that both resolve and reject will accept one parameter. Also resolve should pass that parameter to the successCallback, while reject should pass it to the failureCallback. We thus modify our code as follows, storing two extra variables in the class.
Asynchronous MyPromise
We want to modify our class so that it can handle asynchronous action in the executor. If we run the following example with our current set up, it will not log anything, because when the then method is called, the status is till PENDING.
All we need to do then is to store the callbacks in the class when
status === PENDING
, and call them when the executor is done executing.Calling then multiple times
We want to be able to call the then method multiple times:
. Our implementation currently cannot handle this situation because the second call to then will override the callbacks stored by the first then and etcetera. The only change we need to make to accommodate this is to store all callbacks in an array.
Chaining then methods
Now we want to be able to chain multiple calls to the then method:
Furthermore The second value appearing above in then should be the return value of the first then while the first value is the value passed in by resolve as usual. We have to return a MyPromise object from the then method if we want to chain the then's together.
We have only dealt with the situation when the status of the first Promise in the chain is FULFILLED, and we will take care of the failure and asynchronous mode together later.
Return a MyPromise object and handle chaining cycle
For now we can only return a value in then, but the actual Promise also allows one to return a Promise object.
After making this improvement, we can now return a MyPromise object in our call:
. However, if we allow then method to return a promise there will be a problem. Consider the following situation:
This is called a chaining cycle(if you chase through the code, it is not hard to find that this will cause an infinite "cycle") by the standard Promise package, and we need to throw an exception if we find ourselves doing something like this.
Here we are passing the new MyPromise to resolvePromise for it to check whether there is a chaining cycle. However, we are in the middle of creating the new MyPromise while doing this. In order to resolve this, we have to make resolvePromise asynchronous by invoking setTimeout with 0 delay.
Make the callback parameter of Then optional
Error Handling
Now we want to add error handling to MyPromise. To be more specific, we need to throw an exception when executor and resolvePromise encounters any. We also apply the same logic as before when we deal with the FULFILLED mode to the REJECTED and asynchronous mode.
. This concludes the content of this post and we will be continuing adding more functionality to our MyPromise class in my next post.
The text was updated successfully, but these errors were encountered: