While I have been working on streaming capability for the Klipspringer hub (and refactored existing code in the process), a need for asynchronously providing a resource has repeatedly come up and confused me. It’s confusing because it doesn’t immediately fall in line with Javascript’s promise construct. This post describes the problem in a general way, and a solution.
The situation is that one or more clients asks for a resource from a server, a resource that the server in turn has to obtain from someone else. Obtaining the resourse may succeed or fail, and a client may ask for the resource before or after the server knows whether it can obtain it.
Let the clients call a function requestX, which returns a promise that resolves with x if x can be obtained, and rejects otherwise. The server calls either provideX(x) when it has obtained x, or cancelX() when it has found that it cannot obtain x.
The idea is to create a global promise that requestX() can simply return, and set provideX and cancelX to respectively resolve and reject the promise. But we don’t want an unhandled rejection to happen if cancelX() is called before the client has asked for x. The following is the simplest solution I came up with:
let cancelX = (why) => { requestX = () => Promise.reject(why) } let provideX = () => { throw "provideX called before initialized" } let rejectX = () => { throw "rejectX called before initialized" } const xPromise = new Promise((resolve, reject) => { provideX = resolve; rejectX = reject; }); let requestX = () => { cancelX = rejectX; return xPromise; }
We initialize cancelX to a function that changes requestX to simply return a rejected promise. When requestX is called, it changes cancelX to reject the global promise by setting it to rejectX, which is a variable that exists solely for this purpose and is not part of the public interface.
Small, but a bit of a head-scratcher.