TLDR; JavaScript Promises are not as complicated as they seem!
Ok, so today I’m having fun, and by having fun, I mean, I’ve been tricked into buggering about with some async code in an environment that doesn’t like async code! So, the answer was, of course, promises!
Now, don’t get me wrong, these aren’t like the promises you make your wife or partner, but that got me thinking of some way of making them simple – although I needn’t of worried, promises in this sense are actually (scarily) simple.
Basically, they are a code block, that code block has a number of possible outputs, one is for “resolution”, one is for “rejection” – you pass in your data if needed, then get the promise to do it’s work.
Thinking about it, I think an example would be best here. The signature of the promise is basically:
const myPromise= new Promise((resolve, reject)=> {
if(what_we_expect = happened) {
resolve(return_value);
} else {
reject(reason);
}
});
So what’s happening here? Basically, we are creating a promise, in this promise object, if the variable what_we_expect is equal to happened then, well we’re happy, and we resolve with an arbitrary return_value, if not, we reject the promise with a reason (again, arbitrarily).
This may not make a lot of sense on it’s own, but from here, we can have some fun… let’s write something that may actually make a bit more sense and introduce the real fun, chaining of promises…
function makePromise(value) {
return new Promise((resolve, reject) => {
if(value !== "hello") reject(`Value ${value} is invalid!`);
resolve(value);
});
}
makePromise("hello")
.then((value)=>value + ", promises!")
.then((value)=> {console.log(value);});
You may be asking yourself, “what the hell is that then word about??”, it’s simple, when a promise is resolved, it passes it’s return value out, and you can then chain it in to another promise object (that’s what the then returns) – you can mutate the value, or use it as required, then pass it to another then. This can be done as much as you want, and the result for this one is just Hello, promises! in the console.
There is another point to make here, though, what if there was something that went wrong? That’s just as easy (well, mostly). What you would do here is something like
function makePromise(value) {
// as above
}
makePromise("goodbye")
.then(value=>value+", promises!")
.then(value=>console.log(value))
.catch(error=>console.log(error));
This code will reject the promise, and print out Value goodbye is invalid! which will be picked up in the catch block. Now, regardless of where rejection (or an Error that’s thrown) occurs, this will stop execution of the promise chain at that point, and go straight to the catch statement for any error handling (this also highlights the need for error handling in the original promise itself, or within any then block that requires “finer-grained” error handling).
There is also one other block that is worth mentioning, it is the finally block – this basically gives us the opportunity to clean up any resources, regardless of if the promise was resolved or rejected (and takes the form .finally(()=>{/*code to clean up*/});)
To put it lightly, probably the most useful, and only real-world case I have for creating your own promises is (in this case) the fact that we needed an equivalent to the fetch library creating (as this was not present in some of the environments our software runs in), so we created a similar function (and class) using an XMLHTTPRequest.
function upload(url, data, method = 'POST', onProgress = ()=>{}) {
const uploader = new Uploader(url, method);
if(onProgress) uploader.onProgress(onProgress);
return uploader.upload(data);
}
/**
* Helper class to upload form data to a server endpoint
*/
class Uploader {
/**
* Set a callback that is called when the upload progress changes
* @param callback A callback that is called when the upload progress changes
*/
onProgress(callback) {
this.onProgressCallback = callback;
}
/**
* Create a new uploader
* @param url The endpoint to upload to
* @param method The method to use, either POST or PUT
*/
constructor(url, method = 'POST') {
this.url = url;
this.method = method;
}
/**
* Upload form data to a server endpoint
* @param data The form data to upload
* @returns A promise that resolves to the JSON response from the server
*/
upload(data) {
const request = new XMLHttpRequest();
request.open(this.method, this.url);
request.send(data);
return new Promise((resolve, reject) => {
request.onabort = () => reject('aborted');
request.onerror = () => reject('error');
request.onprogress = (e) => {
this.onProgressCallback && this.onProgressCallback(e.loaded, e.total);
};
request.onreadystatechange = () => {
if (request.readyState === 4) {
if (request.status === 200) {
resolve(JSON.parse(request.responseText));
} else {
reject(request.responseText);
}
}
};
});
}
}
export { upload, Uploader };
And that’s it, well it is for now, next I’ll probably look at async/await – although part of me would prefer to cry for a while into a coffee rather than play in that bull-pen!

