2

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.

quicVO
  • 778
  • 4
  • 13
  • Unless you can invent a time machine... no – Kevin B May 18 '21 at 20:21
  • Not possible, and that is a good thing. Look however at `await`. – trincot May 18 '21 at 20:26
  • @KevinB, I was thinking it might be possible with something from the `fs` module. Also, what do you mean by "a time machine," there are a few ways to have node.js wait for async code to execute async. I saw [this](https://stackoverflow.com/questions/61358303/how-can-we-block-event-loop) and was wondering if there was another way of doing it though. – quicVO May 18 '21 at 20:28
  • no, nothing that gets added to the callback queue can return to the scope that put it there. async/await can make it *appear* as though it does that, but behind the scenes it works similar to .then().then().then() – Kevin B May 18 '21 at 20:31
  • you *could* synchronously execute a child process that then runs more node js that does asynchronous stuff and then returns a value, technically, but really you're just pushing the asynchronous logic elsewhere. async/await can achieve the same end goal without the shenanigans/blocking. – Kevin B May 18 '21 at 20:34
  • @KevinB, I know that you can do that, but how might I "export" a promise to do that. – quicVO May 18 '21 at 20:41
  • Not quite sure what you could possibly mean by that. – Kevin B May 18 '21 at 20:44
  • @KevinB, I mean like a way to communicate with a worker besides postMessage in a way that supports promises and/or functions. – quicVO May 18 '21 at 20:46
  • *"nothing that gets added to the callback queue can return to the scope that put it there"* – Kevin B May 18 '21 at 20:46
  • You can give work to a worker and have a promise that resolves when the worker has done its job. See for example [this answer](https://stackoverflow.com/questions/5408406/web-workers-without-a-separate-javascript-file/48470558#48470558) – trincot May 18 '21 at 21:36
  • @trincot, I don't know what you mean by that. – quicVO May 19 '21 at 02:36

2 Answers2

4

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 if p.then(f, r) will immediately enqueue a Job to call the function f.

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")

Asynchrony

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.

trincot
  • 317,000
  • 35
  • 244
  • 286
  • 1
    Thank you for putting your time into answering the question. I understand that my question is "unpopular." I wasn't concerned about good practice but you have helped me see why I will never use this. Node.js gives the developer a way to synchronously do asynchronous tasks but that doesn't mean that you should do it. Nothing great ever happens when trying to "circumvent" the rules, so I am probably never going to use this. I am marking your answer as the accepted answer because of your explanation but will also leave mine. :) – quicVO May 19 '21 at 15:14
0

I found the npm package deasync. In it, there is a line of code: process._tickCallback(). I implemented it as a function. This will only resolve a promise that has been completely fulfilled. For example:

function halt(promise) {
    let result;
    promise.then(res=>result=res);
    while(!result) process._tickCallback();
    return result;
};

let ans = halt(Promise.resolve(123));
console.log(ans); //=> 123

The feature could disappear whenever as it isn't included in documentation. If the promise is pending, then the while loop will be infinite.

quicVO
  • 778
  • 4
  • 13
  • `process._tickCallback` is in the [list of deprecated APIs](https://github.com/nodejs/node/blob/master/doc/api/deprecations.md). It should not be used. – trincot May 19 '21 at 08:35