Intentionally unleashing Zalgo with synchronous promises

function getUsers(callback) {
if (cache.users) {
callback(null, cache.users); // synchronous callback
} else {
ajax('/api/users', callback); // asynchronous callback
}
}
  1. It can result in unexpected race-conditions.
  2. It changes which call-stack exceptions are thrown into.
  3. It generally makes the function less easy to reason about.
let state = {};getUsers((err, result) => {
if (err) {
console.error(err);
} else {
state.users = result;
}
});
document.querySelector('#hello').addEventListener('click', () => {
console.log('Hello', state.users[0].name);
});
let state = {};state.usersPromise = getUsers();function sayHelloToFirstUser() {
state.usersPromise.then(users => {
console.log('Hello', users[0].name);
});
}
function getFirstUserName(callback) {
return getUsers((err, users) => {
if (err) {
callback(err);
} else {
callback(null, users[0].name);
}
});
}
try {
getFirstUserName(callback(err, name) {
if (err) {
console.error(err);
} else {
console.log('Hello', name);
}
})
} catch (err) {
showErrorPage();
}
function getFirstUserName() {
return getUsers().then(users => {
return users[0].name;
});
}
  • If you need to access the result of a promise, call .then()
  • If you need to handle an error from a promise, call .catch()
  • If you didn’t await the result of the promise, don’t ever rely on the result being available, or the action being complete
  • Everything to do with cross-window or cross-domain communication is by its very nature asynchronous, particularly the postMessage api for messaging between different windows and frames.
  • We wanted to use promises to deal with all of this asynchronicity, given the benefits of safer error handling, reduced boilerplate, and cacheability.
  • Native promises are not something that are going to be supported in all of PayPal’s supported browsers for a long time. As such, we needed to use a promise polyfill.
  • Promise polyfills, in order to be compliant with the promise spec, need to be always-asynchronous, meaning the handler passed to .then() can never be called synchronously.
  • As such, any promise polyfill needs to force asynchronicity if a user tries to resolve it synchronously. The only way to do that consistently across all browsers is to setTimeout on the handler passed to .then().
  • The problem is, setTimeout gets massively de-prioritized by many browsers when the tab isn’t focused… so if you’re in a popup window, and you need to message the parent window to do something in the background, calling setTimeout means you’re not likely to get a response any time soon.
  • This kills the idea of cross-domain components which can seamlessly pass props and call callbacks between multiple frames and windows.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store