Let me start with the fact that I like asynchronous code. I would never wrap async code in a sync wrapper in production, but it is still something that I want to learn how to do. I am talking about specifically in Node.JS, not the browser. There are many ways to access the result of an async function synchronously like using child_process.spawnSync
or a worker and Atomics
. The issue with these ways is this:
let prom = Promise.resolve(4);
// It is now impossible (as far as I know) to access the result of prom synchronously
Promises cannot be sent in a postMessage
call, so a worker cannot access them and wait for them to finish synchronously or at all. One might think, why not do this:
let prom = Promise.resolve(4);
prom.then(res => global.result = res);
while (!global.result) {}; // Do nothing
// Once the loop finishes, the result *would* be available
console.log(global.result); // => 4
Of course, this doesn't work. The event loop waits executes the while loop completely before even beginning to deal with the callback function of prom.then
. This causes an infinite loop. So this makes me ask, "Is there a synchronous task that has to execute not ordinarily to allow for the waiting of a promise?"
Edit
By the way, I totally understand async/await
. If I am using a Node.JS module, then I can just do:
let resolved = await prom;
Or if I am not, I can wrap the whole script in an async
function and do the same thing. However, the goal is to be able to get the access of a result avoiding await OR using await in an asynchronous context that is able to be accessed and waited for from a synchronous context.
This should not be possible. The ECMAScript specification forbids it.
First note that the ECMAScript specification speaks of "jobs" when referring to asynchronous code execution related to promises. For instance, at Promise Objects:
A promise
p
is fulfilled ifp.then(f, r)
will immediately enqueue a Job to call the functionf
.
Jobs are required to only execute when the call stack is empty. For instance, it specifies at Jobs and Host Operations to Enqueue Jobs:
A Job is an abstract closure with no parameters that initiates an ECMAScript computation when no other ECMAScript computation is currently in progress.
Their implementations must conform to the following requirements:
- At some future point in time, when there is no running execution context and the execution context stack is empty, the implementation must:
- [...] Call the abstract closure
You have pointed to Node's undocumented and deprecated process._tickCallback()
method, which clearly violates the specification, as it allows job(s) in the promise job queue to execute while there is a non-empty call stack.
Using such constructs is thus not good practice, as other code cannot rely any more on the above principles of asynchronous job execution.
As to process._tickCallback()
specifically: if it is used to allow a promise to call its then
callbacks, this will not "work" when that promise depends on some other asynchronous API, as for instance setTimeout
. For instance, the following code snippet will loop for ever, as the timeout Job never gets executed:
let result = 0;
new Promise(resolve =>
setTimeout(resolve, 10)
).then(() => result = 1);
while(!result) process._tickCallback();
console.log("all done")
In JavaScript asynchronous code runs (must run) when the call stack is empty. There should be no attempt to make it work differently. The coder should embrace this pattern fully.