I'm implementing a data structure in memory that shadows part of a large data structure stored somewhere out on the web. Let's say the data structures in question are binary trees. I want the in-memory tree to initially consist of just the root node, and it should grow lazily by fetching nodes from the web on demand, as the user (or algorithm) explores.
One natural way to do this is for the tree node data type to provide methods getLeftChild()
, getRightChild()
,
each of which immediately returns a promise for the respective child node.
When getLeftChild()
is called on a tree node whose left child is already in memory,
it returns a promise that is already-resolved with the cached child;
otherwise it initiates a fetch of the child (if not already initiated by an earlier call) and returns a promise for it,
and when the fetched child eventually comes back from the web, the fetched child will be saved in memory for the future and used to resolve the promise.
So, to print the node 5 levels down the left branch, I'd say:
root.getLeftChild()
.then(child0 => child0.getLeftChild())
.then(child00 => child00.getLeftChild())
.then(child000 => child000.getLeftChild())
.then(child0000 => child0000.getLeftChild())
.then(child00000 => {
console.log("child00000 = ", child00000);
});
or (thanks @Thomas):
const lc = node => node.getLeftChild();
Promise.resolve(root)
.then(lc).then(lc).then(lc).then(lc).then(lc)
.then(child00000 => {
console.log("child00000 = ", child00000);
});
Or, the same thing using async/await
:
(async()=>{
let child0 = await root.getLeftChild();
let child00 = await child0.getLeftChild();
let child000 = await child00.getLeftChild();
let child0000 = await child000.getLeftChild();
let child00000 = await child0000.getLeftChild();
console.log("child00000 = ",child00000);
})();
This all works fine, and the calling code doesn't look too horrible in either case.
My only misgiving is that, when exploring within parts of the binary tree (or any similar linked data structure) that are already in memory, I don't want to suffer the overhead of initiating a new microtask every time I want to go from one node to a neighbor in the in-memory data structure. Think of an algorithm whose core computation does millions of such link-following operations.
Promises/A+ does indeed require a new microtask (at least) for each then
callback execution:
2.2.4 onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1].
I believe async/await
has a similar requirement.
What I'd like to know is: what's the easiest/cleanest way to make
a Promise-like object that behaves exactly like a Promises/A+ promise, except for clause 2.2.4?
I.e. I want it to have a then
(or then
-like) method that is "synchronous-when-available", so that the first code snippet above
will execute in one shot without yielding the execution context.
To avoid naming issues/confusion, I'm happy to refrain from calling my synchronous-when-available accessor then
(which is effectively a reserved word nowadays thanks to Promises/A+); instead, I'll call it thenOrNow
.
And I'll call my hypothetical type/implementation PromiseOrNow
.
Would I have to write PromiseOrNow
from scratch, or is there a neat and reliable way to leverage an existing Promises/A+ implementation such as native Promise
?
Notice that, since I'm not planning to mess with anything named "then
",
PromiseOrNow
could incidentally be Promises/A+ compliant, if that turns out to be a good way of doing it.
Perhaps it would be a prototype interited from the native Promise.prototype
.
These properties would be nice in some ways, but they are not requirements.