I am rolling my own JavaScript promise. Sometimes, when you really want to understand something, it’s worth trying to build it yourself. That is what I do with promises in this article. I am trying to build a very basic ‘Promise’. At first there were callbacks … Let’s begin with the basics, before anybody heard of promises, we had callbacks. Take a look at the following snippet. const testUrl = ' 'const fetchData = (url, callback) => {let xhr = new XMLHttpRequest()xhr.onreadystatechange = () => {if (xhr.readyState == 200 ) {callback(xhr.responseText)}}xhr.open(url)xhr.send()}fetchData(testUrl, response => console.log(response) ) https://api.github.com/users/odemeulder What does it do? It creates a very simple that takes a url, reads it using the and allows a callback function to do something with the result. And in the last line, we call our new function; as a callback we provide a function that prints out the response. Very simple. If you are not familiar with , don’t fret, it is not important to understand for the rest of the article. Just know that it is the object that the browser provides to to perform requests. It is an asynchronous function, it takes a callback when the event fires. function XMLHttpRequest XMLHttpRequest JavaScript http onreadystatechange What does a promise look like? Before diving into writing our own promise, let’s see what the end result should look like. What is the call signature? function fetchDataReturningPromise(url) {// what happens here?} function functionWithDelay(arg) {// what happens here?} fetchDataReturningPromise(url).then(response => console.log(response))// orfunctionWithDelay(arg1).then(performFollowUpOperation)// orfunctionWithDelay(arg1).then((resolvedValue) =>PerformFollowUpOperation(resolvedValue)) What do we have here? Let’s focus on the third example. We have a function with a delay; something inside the function makes an asynchronous call. It could be a timeout, an http request, a database call. That function returns ‘something’. That something has a method, so ‘something’ must be an object. In fact ‘something’ will be a function. then The function takes one argument, in the form of a function or a callback. And the callback passed into the function does not get executed until is done. In our third example the function we pass to is labeled . Note that takes one argument ( ) and that argument is provided by . Lots to unpack here. Pause, and re-read previous paragraph (if you’d like to). then then functionWithDelay then performFollowUpOperation performFollowUpOperation resolvedValue functionWithDelay In summary: Define a function with a next function. 2. Define a function with a delay that returns a function with a next function ( ) functionWithDelay 3. The function with a function needs to take another function as an argument. That follow-up function cannot be executed until the function with delay is done. ( ) next performFollowUpOperation 4. The function with delay must somehow pass a value to the follow-up function. So now we need to fill in , first we need to define that thing that has a function. functionWithDelay next First naieve attempt Let’s make a first attempt. 1: function FunctionWithAThenFunction() {2: this.then = function (followUpFunction) {3: followUpFunction()4: }5: } 6: function functionWithDelay(param1) {7: let xhr = new XMLHttpRequest()8: xhr.onreadystatechange = () => {9: if (xhr.readyState == 200) callback(xhr.responseText)10: }11: xhr.open(param1)12: xhr.send()13: return new FunctionWithAThenFunction()14: } All right, we have a function with a function. Our returns a function with a function. But. What is on line 9? Somehow that should end up in the callback. That is not happening. Therefor, the is going to execute right away and not wait until is done. then functionWithDelay then callback followUpFunction followUpFunction functionWithDelay How do we get that to be ? followUpFunction callback Second attempt Let’s take a look at . What is characteristic about a function with a delay? Or what is characteristic about asynchronous functions? They will often take a callback in some way. So let’s try to re-write that function as accepting a callback, such that we can access that callback easily. functionWithDelay And then let’s rewrite our and let it accept an function, more specifically an asynchronous function with callback. FunctionWithAThenFunction 1: function FunctionWithAThenFunction(aFnWithDelayAndACallback){2: let nextThingToDo3: this.then = function ( followUpFunction) {4: nextThingToDo = followUpFunction5: }6: aFnWithDelayAndACallback( nextThingToDo )7: }8: function functionWithDelay(param1) {9: const httpRequestWithCallback = function (callback) {10: let xhr = XMLHttpRequest()11: xhr.onreadystatechange = () => {12: if (xhr.readyState == 200) callback(xhr.responseText)13: }14: xhr.open(param1)15: xhr.send()16: }17: return new FunctionWithAThenFunction(httpRequestWithCallback)18: }19: functionWithDelay('some argument').then(console.log) Lines 1–7 define our . That is the function that will be returned by the function with a delay. And it expects a function as an argument. More specifically a function with a callback. The function then sets the callback in a local variable ( ). And that function really does one thing and that is to call the function with the callback, in our example the function labeled . FunctionWithAThenFunction then nextThingToDo aFnWithDelayAndACallback Lines 8–18 define our . In our example the function that does an request and returns something with a function. And that something with a function is a new instance of . It looks a little different than what we did above. We create a new local temporary function named , which accepts a callback argument and wraps the entire code from the prior examples. That new local function is then passed as an argument to . functionWithDelay http then then FunctionWithAThenFunction httpRequestWithCallback XMLHttpRequest FunctionWithAThenFunction Now, if you try to run this (line 19), it will still not accomplish what we are trying to do. If you follow along, you will see that is as we are calling it before we get a chance to assign it a value. We are missing one thing and that is some kind of status. nextThingToDo undefined Third Attempt Let’s add a variable that keeps track of the status of execution. 1: function FunctionWithAThenFunction(aFnWithDelayAndACallback){2: let nextThingToDo3: let status = 'pending'4: this.then = function ( followUpFunction) {5: if (status === 'pending') {6: nextThingToDo = followUpFunction7: }8: else {9: followUpFunction()10: }11: }12: aFnWithDelayAndACallback( (newValue) => {13: status = 'resolved'14: nextThingToDo(newValue)15: })16: }17: function functionWithDelay(param1) {18: const httpRequestWithCallback = function (callback) {19: let xhr = XMLHttpRequest()20: xhr.onreadystatechange = () => {21: if (xhr.readyState == 200) callback(xhr.responseText)22: }23: xhr.open(param1)24: xhr.send()25: }26: return new FunctionWithAThenFunction(httpRequestWithCallback)27: }28: functionWithDelay('some argument').then(console.log) You see that we declare a variable (line 3). This way we cover all of our basis. If is called before the main function is done, we store the in a variable, if not we can safely execute it. But it does not have the value from the main function. Let’s fix that. status then followUpFunction 1: function FunctionWithAThenFunction(aFnWithDelayAndACallback){2: let nextThingToDo, value3: let status = 'pending'4: this.then = function ( followUpFunction) {5: if (status === 'pending') {6: nextThingToDo = followUpFunction7: }8: else {9: followUpFunction(value)10: }11: }12: aFnWithDelayAndACallback( (newValue) => {13: status = 'resolved'14: value = newValue15: nextThingToDo(newValue)16: })17: }18: function functionWithDelay(param1) {19: const httpRequestWithCallback = function (callback) {20: let xhr = XMLHttpRequest()21: xhr.onreadystatechange = () => {22: if (xhr.readyState == 200) callback(xhr.responseText)23: }24: xhr.open(param1)25: xhr.send()26: }27: return new FunctionWithAThenFunction(httpRequestWithCallback)28: }29: functionWithDelay('some argument').then(console.log) Here we declare a new local variable named . (line 2) When we resolve the main function we set that value. So it can be passed as a parameter to on line 9. value followUpFunction Speaking of resolving, let’s add re-write this for clarity. No change in functionality with the previous sample. 1: function FunctionWithAThenFunction(aFnWithDelayAndACallback){2: let nextThingToDo, value3: let status = 'pending'4: this.then = function ( followUpFunction) {5: if (status === 'pending') {6: nextThingToDo = followUpFunction7: }8: else {9: followUpFunction(value)10: }11: }12: const resolve = newValue => {13: value = newValue14: status = 'resolved'15: if (nextThingToDo) nextThingToDo(newValue)16: }17: aFnWithDelayAndACallback(resolve)17: }18: function functionWithDelay(param1) {19: const httpRequestWithCallback = function (callback) {20: let xhr = XMLHttpRequest()21: xhr.onreadystatechange = () => {22: if (xhr.readyState == 200) callback(xhr.responseText)23: }24: xhr.open(param1)25: xhr.send()26: }27: return new FunctionWithAThenFunction(httpRequestWithCallback)28: }29: functionWithDelay('some argument').then(console.log) Here we introduce the local resolve function for clarity on lines 12–16. It is subsequently called on line 17. Putting it all together We’ve come a long way. Let’s re-write the last example one more time and change the function names a little bit. becomes FunctionWithAThenFunction Promise becomes functionWithDelay fetchData becomes httpRequestWithCallback httpRequest becoms aFnWithDelayAndACallback fn function Promise(fn){let nextThingToDo, valuelet status = 'pending'this.then = function ( callback ) {if (status === 'pending') {nextThingToDo = callback}else {callback(value)}}const resolve = newValue => {value = newValuestatus = 'resolved'if (nextThingToDo) nextThingToDo(newValue)}fn(resolve)}function fetchData(url) {const httpRequest = function (callback) {let xhr = XMLHttpRequest()xhr.onreadystatechange = () => {if (xhr.readyState == 200) callback(xhr.responseText)}xhr.open(url)xhr.send()}return new Promise(httpRequest)} const testUrl = ' 'fetchData(testUrl).then(console.log) https://api.github.com/users/odemeulder The code can now be used in other functions. Let’s say you wanted to have a function similar to that returned a promise. Let’s call the function and it would accept a delay time in milliseconds. Promise setTimeout Wait function Wait(milliSeconds) {const fn = (callback) => setTimeout(callback, milliSeconds)return new Promise(fn)}Wait(5000).then(() => console.log('we waited 5 seconds')) Or let’s say you want to use the example in Node. In Node won’t work because that is an object provided by the browser, you’d have to use Node’s built in module. httpRequest XmlHttpRequest https const https = require('https')const testUrl = ' 'function fetchDataForNode(url) {const fn = callback => {const options = {url: url,headers: { 'User-Agent': 'request' }}https.get(options, response => {let retresponse.on('data', chunk => ret += chunk)response.on('end', () => callback(ret))})return new Promise(fn)}fetchDataForNode(testUrl).then(console.log) https://api.github.com/users/odemeulder Again, don’t fret over the technicalities of how works in node. The important part is: create a function that takes a callback and wraps an asynchronous call, and pass that function to your constructor. https.get Promise Next Steps There is obviously much more to a promise than what we just outlined. This article is long enough already. But here are some improvements / additions. One thing is that the function should return a new promise. That allows you to chain promises. then doSomething().then(somethingElse).then(aThirdThing) And the other big thing that we did not go into is error handling. Our function should also implement a function. That function would take a function as a parameter and that function would be called in case of any errors. Promise catch Conclusion We created a very basic Promise object, and I showed you three examples of how to create a function that returns a promise ( for browser http requests, for http requests in Node and ). In each case, you create a function that accepts a callback, create a new function and pass the function as callback. fetchData fetchDataForNode wait Promise I welcome any feedback as well as applause.