Ever wondered how JavaScript Promise works internally? Obviously there is some native browser support involved, right? Nope! We can implement the Promise interface using pure JavaScript, examples are libraries like or . And it’s much simpler than you may think, we can do so in only 70 lines of code! This will help with gaining a deeper insight into Promises by demystifying the underlying formation. Can also serve as a good interview question, if you are an evil employer (don’t be!). Let’s dig into it! Bluebird Q First thing that you notice is that a Promise has three states, so should we: states = { : , : , : }; const pending 'Pending' resolved 'Resolved' rejected 'Rejected' Using a class sounds reasonable since we should be able to create a . Ah, and let’s name our class something else! It’s an object that can or . Hmm, that is capable of those! Let’s go with that: new Promise() resolve reject google thinks Nancy { (executor) { resolve = { .state = states.resolved; }; reject = { .state = states.rejected; }; .state = states.pending; { executor(resolve, reject); } (error) { reject(error); } } } class Nancy constructor const => () this const => () this this try catch Now errors thrown during Promise execution like are captured by . We can also do things like except… it doesn’t do what we want! would be changed to , but we also need to be ! Let’s change the and definitions: new Nancy(resolve => { throw new Error(); }) reject new Nancy(resolve => resolve(42)) this.state states.resolved this.value 42 resolve reject getCallback = value => { .value = value; .state = state; }; resolve = getCallback(states.resolved); reject = getCallback(states.rejected); const => state this this const const We used a Higher-Order-Function, , to avoid repeated code for and . Our now works as expected. getCallback resolve reject resolve(42) Time for the beefier stuff! The infamous “ ”s. The interface allows a Promise to be chained, which means it should return a Promise. First we create and syntactic sugars: then then Nancy.resolve Nancy.reject { ... static resolve(value) { Nancy( resolve(value)); } reject(value) { Nancy( reject(value)); } } class Nancy return new => resolve static return new ( ) => _, reject This allows us to write our as . Now let’s see what we expect from then: new Nancy(resolve => resolve(42)) Nancy.resolve(42) p = Nancy.reject( ) .then( .log( )) .then( .log( )) .then( .log( )); carry = input => { .log(input); output; }; p = Nancy.resolve( ) .then(carry( )) .then(carry( )) .then(carry( )); // Ignore let 42 => () console 'why' // ignored => () console 'you' // ignored => () console 'ignoring me?!' // ignored! // p is a Nancy // p.state is states.rejected // p.value is 42 const => output console return // Chain 0 1 // logs 0 2 // logs 1 3 // logs 2 // p is a Nancy // p.state is states.resolved // p.value is 3 has different behaviour in and states. That means lots of “ ”s, or… maybe we can do better? then rejected resolved if { (executor) { members = { [states.resolved]: { : states.resolved, then: Nancy.resolve(onResolved( .value)) }, [states.rejected]: { : states.rejected, then: }, [states.pending]: { : states.pending } }; changeState = .assign( , members[state]); getCallback = value => { .value = value; changeState(state); }; resolve = getCallback(states.resolved); reject = getCallback(states.rejected); changeState(states.pending); { executor(resolve, reject); } (error) { reject(error); } } resolve(value) { Nancy( resolve(value)); } reject(value) { Nancy( reject(value)); } } class Nancy constructor const state // Chain mechanism => onResolved this state // Ignore mechanism => _ this state const => state Object this const => state this const const try catch static return new => resolve static return new ( ) => _, reject As you see, no ! We’ve implemented a mechanism for “shifting the gear”, our differently . That function in line 18 does what all those condition checks would do for us, voila! if state machine behaves on each gear changeState One caveat: . This should result in a state, but throws the error instead. Not to worry! Our friends at TC39 have that we are just going to implement. Introducing : Nancy.resolve(42).then(() => { throw new Error(); }) rejected a proposal Nancy.try { (executor) { tryCall = Nancy.try( callback( .value)); members = { [states.resolved]: { ... then: trycall }, ... }; ... } ... static (callback) { Nancy( resolve(callback())); } } class Nancy constructor const => callback => () this const try return new => resolve You may think implementing is about as much hassle. Think again! It’s as easy as inverting . catch then [states.resolved]: { ... then: tryCall, : }, [states.rejected]: { ... then: , : tryCall } catch => _ this => _ this catch Now this works: anything = { ( ); }; throwSomethingWrong = { .log( ); ( ); }; p = Nancy.reject( ) .catch( value) .catch(anything) .catch(anything) .then( .log(value)) .then(throwSomethingWrong) .catch(throwSomethingWrong) .catch( ); const => () throw new Error 'I can be anything because I should never get called!' const => () console 'not ignored!' throw new Error 'Something went wrong...' const 42 => value // resolves // ignored // ignored => value console // logs 42 // logs not ignored! // logs not ignored! => () 24 // resolves // p is a Nancy // p.state is states.resolved // p.value is 24 Two other things that we should fix: p = Nancy( { resolve( ); reject( ); resolve(); }); p .then( Nancy.reject(value)) .catch( .log(value)); p = Nancy.reject(Nancy.resolve( )); let new ( ) => resolve, reject 42 24 // ignored // ignored => value // rejects => value console // logs 42 42 // p.state is states.rejected // p.value is a Nancy resolved to 42 Ignoring subsequent calls to and and unpacking a Promise on (and not ). We address both these issues in by moving the previous assignment and call to a new function, : resolve reject value resolve reject getCallback value changeState apply apply = { ( .state === states.pending) { .value = value; changeState(state); } }; getCallback = value => { (value Nancy && state === states.resolved) { value.then( apply(value, states.resolved)); value.catch( apply(value, states.rejected)); } { apply(value, state); } }; const ( ) => value, state // Ignore subsequent calls to resolve and reject if this this const => state // Unpack on resolve if instanceof => value => value // Either 'then' or 'catch' will happen here, not both // No need for more ifs! else Well, no escaping the “ ”s this time I’m afraid… until the day that comes around! if match It’s probably time to acknowledge the elephant in the room. Where’s in all this? Right, maybe you think it’s going to be a lot of work? Save for a good laugh (spoiler: we are 7 lines away)! async In order to create an scenario, we first write the version of the popular function: async Nancy delay delay = Nancy( setTimeout(resolve, milliseconds)); logThenDelay = total => { .log( const => milliseconds new => resolve const => milliseconds console ` ${total / 1000.0} seconds!`); return delay(milliseconds) .then(() => total + milliseconds); }; logThenDelay(500)(0) / / logs 0 seconds! .then(logThenDelay(500)) / / after 0.5 seconds, logs 0.5 seconds! .then(logThenDelay(500)) / / after 1 second, logs 1 seconds! .then(logThenDelay(500)); / / after 1.5 seconds, logs 1.5 seconds! We should also accommodate for multiple and on a single Promise: then catch p = delay( ); p.then( .log( )); p.then( .log( )); p.then( .log( )); p = p.then( Nancy.reject()); p.catch( .log( )); p.catch( .log( )); p.catch( .log( )); let 500 => () console '1st then!' // after 0.5 seconds, logs 1st then! => () console '2nd then!' // after 0.5 seconds, logs 2nd then! => () console '3rd then!' // after 0.5 seconds, logs 3rd then! => () => () console '1st catch!' // after 0.5 seconds, logs 1st catch! => () console '2nd catch!' // after 0.5 seconds, logs 2nd catch! => () console '3rd catch!' // after 0.5 seconds, logs 3rd catch! The problem is, our code knows how to handle and on a or state, we just need to hold up until the state arrives there. Our bigger problem is that we need to return a Promise right now! Hmm, well, those are not really problems, they are actually the solution! Let’s do what we just said: then catch resolved rejected { (executor) { ... const laterCalls = []; callLater = callback => Nancy( laterCalls.push( resolve(getMember()(callback)))); members = { ... [states.pending]: { ... then: callLater( .then), : callLater( .catch) } }; ... const apply = { ( .state === states.pending) { ... for ( laterCall laterCalls) { laterCall(); } } }; ... } ... } class Nancy constructor const => getMember new => resolve => () const => () this catch => () this ( ) => value, state if this const of We cached both the call to / and returned Promise’s in a . We call these at the end of later. Boom! then catch resolve laterCall apply We may not be particularly proud of the verbose code of our definition. Not to worry though, one day we will re-write it with the syntax. callLater pipe If you find confusing, you can read more on how it works with an example. callLater here Here’s our code in its final glory: states = { : , : , : }; { (executor) { tryCall = Nancy.try( callback( .value)); laterCalls = []; callLater = callback => Nancy( laterCalls.push( resolve(getMember()(callback)))); members = { [states.resolved]: { : states.resolved, : tryCall, : }, [states.rejected]: { : states.rejected, : , : tryCall }, [states.pending]: { : states.pending, : callLater( .then), : callLater( .catch) } }; changeState = .assign( , members[state]); apply = { ( .state === states.pending) { .value = value; changeState(state); ( laterCall laterCalls) { laterCall(); } } }; getCallback = value => { (value Nancy && state === states.resolved) { value.then( apply(value, states.resolved)); value.catch( apply(value, states.rejected)); } { apply(value, state); } }; resolve = getCallback(states.resolved); reject = getCallback(states.rejected); changeState(states.pending); { executor(resolve, reject); } (error) { reject(error); } } resolve(value) { Nancy( resolve(value)); } reject(value) { Nancy( reject(value)); } (callback) { Nancy( resolve(callback())); } } const pending 'Pending' resolved 'Resolved' rejected 'Rejected' class Nancy constructor const => callback => () this const const => getMember new => resolve => () const state then catch => _ this state then => _ this catch state then => () this catch => () this const => state Object this const ( ) => value, state if this this for const of const => state if instanceof => value => value else const const try catch static return new => resolve static return new ( ) => _, reject static try return new => resolve Hey, we did it! A functional named in exactly 70 lines of clean code. Hooray! Promise Nancy Another good exercise is to implement and , but I leave that to the beloved reader. You can find the code for this article in . Hope it‘s been an interesting read! Nancy.all Nancy.race this repository Let me know your feedback in the comments section.