How to Synchronously Determine a JavaScript Promise's State

How can I synchronously determine a JavaScript Promise's state?

No such synchronous inspection API exists for native JavaScript promises. It is impossible to do this with native promises. The specification does not specify such a method.

Userland libraries can do this, and if you're targeting a specific engine (like v8) and have access to platform code (that is, you can write code in core) then you can use specific tools (like private symbols) to achieve this. That's super specific though and not in userland.

How to check if a Promise is pending

You can attach a then handler that sets a done flag on the promise (or the RunTest instance if you prefer), and test that:

     if (!this.promise) {
this.promise = this.someTest();
this.promise.catch(() => {}).then(() => { this.promise.done = true; });
retVal = true;
}

if ( this.promise.done ) {
this.promise = this.someTest();
this.promise.catch(() => {}).then(() => { this.promise.done = true; });
retVal = true;
}

Notice the empty catch() handler, it's crucial in order to have the handler called regardless of the outcome of the promise.
You probably want to wrap that in a function though to keep the code DRY.

Get value of resolved Promise in sync

No, it is not possible to do this. This is by design.

The Promise A+ specification is meant to be used as a simple, consistent way to deal with asynchronous operations. One of the constraints is that passing a new callback on to then() on an already resolved promise will always execute on a later tick in the event loop, so things are consistent.

Adding a secondary way to inspect promise results would not have been impossible. It would probably have been quite easy to do so, but there's at least 2 problems with adding this to the specification:

  1. If you're looking to build a specification, you want it to be as simple as possible. The specification itself actually only defines the then() function.
  2. By adding this feature, you're opening the door to hordes of developers getting even more confused about something that's already hard to grok.

Promises and asynchronous operations are hard for people to understand. I see questions here daily about promises and not 'getting' it. If non-async way to access promise results would be added to the default promise, I'd imagine that this would be an even larger amount. It's good to try and enforce 'the right way' of doing things.

However, this decision is not simply made for you. You're not stuck there. You can very easily build your own version of a promise that has this feature, and still works with existing promises. As long as your object has a then() function that behaves according to Promises/A+ you can do with the rest of the object whatever you want.

How to access the result of a Promise synchronously?

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.

Promise is synchronous or asynchronous in node js

The function you pass into the Promise constructor runs synchronously, but anything that depends on its resolution will be called asynchronously. Even if the promise resolves immediately, any handlers will execute asynchronously (similar to when you setTimeout(fn, 0)) - the main thread runs to the end first.

This is true no matter your Javascript environment - no matter whether you're in Node or a browser.

console.log('start');const myProm = new Promise(function(resolve, reject) {  console.log('running');  resolve();});myProm.then(() => console.log('resolved'));console.log('end of main block');

How would you display a promise object's state and value when overriding console.log

You can use this trick from this other SO answer to handle the arguments that are of type Promise like so. The trick requires the custom console.log to be asynchronous like so:

function promiseState(p) {
const t = {};
return Promise.race([p, t])
.then(v =>
(v === t) ? { state: "pending" } : { state: "fulfilled", value: v },
() => { state: "rejected" }
);
}

console.log = async function(...args) {
for (let arg of args) {
if (arg instanceof Promise) {
let state = await promiseState(arg);
divconsole.innerHTML += `Promise { <state>: "${ state.state }"${ state.state === "fulfilled" ? ', <value>: ' + state.value : '' } }<br>`;
} else if (typeof arg === 'object') {
divconsole.innerHTML += 'console.log arg: ' + String(arg) + '<br>';
}
// add more else-ifs to handle strings, numbers, booleans, ... etc
}
}

The custom console.log needs to be asynchronous because it wraps the promise in another promise. This is the only way you can do it.

Explanation:

The trick is to race the promise p against a resolved one (that yields an object {}), if the promise p is already fulfilled its value will be used and since its value can't be the same as that of the other promise (the one we are racing against, the one that yields {}), we determine that the promise has fulfilled. If the value we get is that of the other promise (the object {}) meaning that our resolved promise won the race, we can be sure that the promise p has not yet fulfilled.

If we get an error (meaning the catch callback gets called), the the promise p is rejected.

Demo:

function promiseState(p) {
const t = {};
return Promise.race([p, t])
.then(v =>
(v === t) ? { state: "pending" } : { state: "fulfilled", value: v },
() => { state: "rejected" }
);
}

console.log = async function(...args) {
let divconsole = document.getElementById('main');

for (let arg of args) {
if (arg instanceof Promise) {
let state = await promiseState(arg);
divconsole.innerHTML += `Promise { <state>: "${ state.state }"${ state.state === "fulfilled" ? ', <value>: ' + state.value : '' } }<br>`;
} else if (typeof arg === 'object') {
divconsole.innerHTML += 'console.log arg: ' + String(arg) + '<br>';
}
}
}

const pause = sec => new Promise(r => setTimeout(r, 1000 * sec));

;(async function() {
await new Promise(resolve => document.addEventListener('DOMContentLoaded', resolve));
await pause(1);
let divconsole = document.getElementById('main');

let y = pause(0.5);
await console.log(y);
await y;
await console.log(y);
})();
<div id="main" style="width:100%;height:auto;background-color:lightblue;"></div>

How to tell if a promise is resolved?

The ES6 Promise constructor does not have a property that can tell you the state of the promise. You need to do something like this:

import p from './promise.js'
var isResolved = false;
p.then(function() {
isResolved = true;
});

// ... At some point in the future.
console.log('p is resolved?', isResolved);

There is an internal property called PromiseState but you can't access it. Here is the spec.

How to create a queue of promises that will be sync when it's empty?

I would not even use Promise.all here - wait only for the first promise in the queue.

const term = {
/** an array while a promise is currently awaited, null when `echo` can be synchronous */
_queue: null,
echo(value) {
if (this._queue) {
this._queue.push(value);
} else {
this._echo(value);
}
},
/** returns a promise if the `value` is asynchronous, undefined otherwise */
_echo(value) {
try {
if (typeof value == "function") {
value = value();
}
if (typeof value.then == "function") {
this._queue ??= [];
return Promise.resolve(value).then(console.log, console.error).finally(() => {
while (this._queue.length) {
if (this._echo(this._queue.shift())) {
return;
}
}
this._queue = null;
});
} else {
console.log(value);
}
} catch(err) {
console.error(err);
}
}
};

function render(text, delay) {
return new Promise(resolve => {
setTimeout(() => resolve(text), delay);
});
}
term.echo('foo');
term.echo(() => render('lorem', 1000));
term.echo('bar');
term.echo(() => render('ipsum', 1000));
term.echo('baz');
term.echo(() => render('dolor', 1000));
term.echo('quam');

setTimeout(function() {
// this should render immediately because all promises
// would be already resolved after 5 seconds
term.echo('lorem ipsum');
console.log('end');
}, 5000);

console.log('echos queued');


Related Topics



Leave a reply



Submit