JS Callbacks to Promises

Sam Thorogood
Feb 5, 2018 · 5 min read

Background on Promises

const p = window.fetch('/some/url');
p.then((response) => response.json()).then((json) => {
console.info('got a response', json);
});

Async and await

const response = await window.fetch('/some/url');
const json = await response.json();
console.info('got a response', json);

Converting old APIs to Promises

1. Callbacks

window.setTimeout(() => {
// do something after 1000ms
}, 1000);
// becomes
const promiseSetTimeout = (ms) =>
new Promise((resolve) => window.setTimeout(resolve, ms));
// usage
console.info('waiting 1s');
await promiseSetTimeout(1000);
console.info('it was a whole second!');
const promiseRequestAnimationFrame = () =>
new Promise((resolve) => window.requestAnimationFrame(resolve));
const promiseRequestIdleCallback = () =>
new Promise((resolve) => window.requestIdleCallback(resolve));
// usage
const timestamp = await promiseRequestAnimationFrame();

2. Complex APIs with events

const insertScript = (path) =>
new Promise((resolve, reject) => {
const s = document.createElement('script');
s.src = path;
s.onload = () => resolve(s); // resolve with script, not event
s.onerror = reject;
document.body.appendChild(s);
});
// usage
const alreadyLoadedScript = await insertScript('path/to/script.js');
const readFile = (file) => new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsArrayBuffer(file); // or a different mode
});
// usage
const input = document.body.querySelector('input[type="file"]');
const arrayBuffer = await readFile(input.files[0]);

3. Single-use listeners

const waitForMove = (element, transform) =>
new Promise((resolve) => {
element.addEventListener(
'transitionend', () => resolve(), {once: true});
element.style.transform = transform;
});
// usage
const ball = document.getElementById('ball');
await waitForMove(ball, 'translate(10px)');

4. NodeJS-style APIs

const promiseRead = (path, options) =>
new Promise((resolve, reject) => {
fs.readFile(path, options, (error, data) => {
error ? reject(error) : resolve(data);
});
// usage
const data = await promiseRead('file.blah');

Cancelable Promises

await promisifiedVersionOfThing();

Opinion time

But if you really want…

const promiseSetTimeoutWithCancel = (ms) => {
let id = 0;
const p = new Promise((resolve) => {
id = window.setTimeout(resolve, ms);
});
p.cancel = () => window.clearTimeout(id);
return p;
};
// usage
const p = promiseSetTimeoutWithCancel(1000);
p.cancel(); // whoops, give up
const promiseSetTimeoutReturnBoth = (ms) => {
let id = 0;
return {
promise: new Promise((resolve) => {
id = window.setTimeout(resolve, ms);
}),
cancel: () => window.clearTimeout(id),
};
};
// usage
const {promise, cancel} = promiseSetTimeoutReturnBoth(1000);
cancel();

Sam Thorogood

Written by

🎅🎄 Santaware Engineer at Google in sunny Sydney, Australia 🇦🇺 —evangelizes Chrome and the mobile web!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade