When does the immediately resolved promise execute the resolved callback? - javascript

Please consider this code:
function testPromise() {
resolver = function(resolve, reject) {
resolve('foo');
console.log('#resolver');
}
setTimeout(() => console.log('Timeout'), 0);
console.log('Before');
const p = new Promise(resolver);
console.log(p);
p.then((value) => console.log('#then1:', value));
p.then((value) => console.log('#then2:', value));
console.log('After');
}
Running this function gives the following output in Firefox console:
Before
#resolver
Promise { <state>: "fulfilled", <value>: "foo" }
After
#then1: foo
#then2: foo
Timeout
Although the promise executor doesn't do anything asynchronous and calls the resolve callback parameter right away, and the console output confirms that the promise state is fulfilled, nevertheless the callback function that is passed to promise's then() method is called later, after execution of the currently running code is finished. I could get the same results if the resolve() call would be put in setTimeout: setTimeout(() => resolve('foo'), 0);
I'd like to understand more about this behavior: when exactly this then callback is called? Immediately after the currently executed code exits? Or there could be some other queued-up code that gets executed before? From the test code above, it can be observed that the zero-time timeout callback is executed later.

Related

Unexpected Promise.all() fail fast behavior - continues to execute after rejection

From MDN:
Promise.all is rejected if any of the elements are rejected. For example, if you pass in four promises that resolve after a timeout and one promise that rejects immediately, then Promise.all will reject immediately.
Let's discuss the following code snippet:
(async () => {
try {
const awaited = await Promise.all([
(() => {
console.log('1');
return Promise.reject('2');
})(),
(() => {
console.log('3');
return Promise.resolve('4');
})(),
]);
console.log(awaited);
} catch (error) {
console.log(error);
} finally {
console.log('5');
}
})();
All the promises in the code above are immediately resolved/rejected. I think that the code should execute this way:
First console.log is executed, thus logging '1';
Promise is rejected with the value '2' thus fast-failing Promise.all();
'Catch' clause is executed, logging the rejected value of '2';
'Finally' clause is executed regardless, logging '5';
Instead, I see a '3' also being logged. Why is it so?
Instead, I see a '3' also being logged. Why is it so?
All the other answers try to explain how promises or the event loop work, but in fact it has nothing to do with those.
The argument you pass to Promise.all is an array that contains the results of two function calls. Therefore both functions will be called before Promise.all is called.
Both functions console.log a value and then return a promise.
Promise.all receives an array containing those promises. So before Promise.all does anything, both functions have already been executed and logged 1 and 3 respectively.
Maybe it's easier to see you restructure your code to create the promises beforehand:
(async () => {
try {
const promise1 = (() => {
console.log('1');
return Promise.reject('2');
})();
const promise2 = (() => {
console.log('3');
return Promise.resolve('4');
})()
const awaited = await Promise.all([promise1, promise2]);
console.log(awaited);
} catch (error) {
console.log(error);
} finally {
console.log('5');
}
})();
Just like with any other function, all function arguments are evaluated before the function itself is called. The order of events is rather this:
First console.log is executed, thus logging '1';
Rejected promise with 2 is created.
Second console.log is executed, thus logging '3';
Fulfilled promise with 4 is created.
Array containing both promises is created.
Promise.all is called with array of promises as parameter.
Promise.all processes promises.
'Catch' clause is executed, logging the rejected value of '2';
'Finally' clause is executed regardless, logging '5';
Here is a simpler example: Note that nothing is "done" with the second argument passed to the function and yet the value is still logged, precisely because all arguments are evaluated first:
function process(arg) {
return 'Received ' + arg;
}
const result = process(
(() => {
console.log('1');
return 1;
})(),
(() => {
console.log('3');
return 2;
})(),
);
console.log(result)
This is due to the way that Promises are handled in JavaScript. It is asynchronous in nature and there is no security as to the execution order. Please note that this is not an error but rather a feature of the language.
I would suggest you read more on the issue in this SO question and about the event loop.
The Promise.all() does fast-fail. However, it doesn't abort the other Promises from finishes. This example with timeouts illustrates it a little better.
Promise.all([
Promise.reject(),
new Promise((resolve, reject) => setTimeout(() => { console.log('a'); resolve(); }, 500)),
new Promise((resolve, reject) => setTimeout(() => { console.log('b'); resolve(); }, 2000))
])
.then(() => console.log('done'))
.catch(() => console.log('fail'));
You can see that the Promise.all() calls the catch() almost immediately, and stops waiting for the results of the other, because it won't change the outcome. However, the other two Promises aren't aborted in any way.
If you want to abort them, you'd have to implement your own logic to do that.

Any way to cause a promise to be rejected if it has an uncaught error?

It's easy to forget to use try/catch in an async function or otherwise fail to catch all possible errors when working with promises. This can cause an endless "await" is the Promise is never resolved nor rejected.
Is there any way (such as via a proxy or altering the promise constructor) to cause an async function or other promises to be rejected if there is an uncaught error? The following shows a generalized case. I'm looking for some way to get past the "await" (as in "p" should be rejected when the error is thrown) without fixing "badPromise".
async function badPromise() {
const p = new Promise((res) => {
delayTimer = setTimeout(() => {
console.log('running timeout code...');
if (1 > 0) throw new Error('This is NOT caught!'); // prevents the promise from ever resolving, but may log an error message to the console
res();
}, 1000);
});
return p;
}
(async () => {
try {
console.log('start async');
await badPromise();
console.log('Made it to the end'); // never get here
} catch (e) {
console.error('Caught the problem...', e); // never get here
}
})();```
Promises already reject in the case of an uncaught synchronous error:
in a Promise constructor, for synchronous (thrown) errors
If an error is thrown in the executor, the promise is rejected.
in onFulfilled and onRejected functions, such as in then and catch
If a handler function: [...] throws an error, the promise returned by then gets rejected with the thrown error as its value.
in async functions
Return Value: A Promise which will be resolved with the value returned by the async function, or rejected with an exception thrown from, or uncaught within, the async function.
Your problem here isn't that Promise doesn't handle uncaught errors, it's fundamentally because your error is asynchronous: As far as the Promise is concerned, its executor function is a successful little function that calls setTimeout. By the time your setTimeout handler runs and fails, it does so with its own stack that is unrelated to the Promise object or its function; nothing related to badPromise or p exists within your setTimeout handler other than the res reference the handler includes via closure. As in the question "Handle error from setTimeout", the techniques for catching errors in setTimeout handlers all involved editing or wrapping the handler, and per the HTML spec for timers step 9.2 there is no opportunity to catch or interject an error case for the invocation of the function passed into setTimeout.
Other than editing badPromise, there's almost nothing you can do.
Alternatives:
Modify/overwrite both the Promise constructor and the setTimeout method in sequence, wrapping the Promise constructor's method to save the resolve/reject parameters and then wrapping the global setTimeout method so to wrap the setTimeout handler with the try/catch that invokes the newly-saved reject parameter. Due to the fragility of changing both global services, I strongly advise against any solutions like this.
Create a wrapper higher-order function (i.e. function that returns a function) that accepts a rejection callback and wraps the setTimeout call. This is technically an edit to badPromise, but it does encapsulate what's changing. It'd look something like this:
function rejectOnError(rej, func) {
return (...args) => {
try {
return func(...args);
} catch (e) {
rej(e);
}
};
}
async function badPromise() {
const p = new Promise((res, rej) => { // save reject
delayTimer = setTimeout(rejectOnError(rej, () => { // to use here
console.log('running timeout code...');
if (1 > 0) throw new Error('Now this is caught');
res();
}), 1000);
});
return p;
}
badPromise().catch(x => console.error(`outer: ${x}`));
console.log('bad promise initiated');
The underlying issue is that timer callbacks run as top level code and the only way to detect errors in them is to listen for global error events. Here's an example of using a global handler to detect such errors, but it has issues which I'll discuss below the code:
"use strict";
let delayTimer; // declare variable
async function badPromise() {
const p = new Promise((res) => {
let delayTimer = setTimeout(() => { // declare variable!!!
console.log('running timeout code...');
if (1 > 0) throw new Error('This is NOT caught!'); // prevents the promise from ever resolving, but may log an error message to the console
res();
}, 1000);
});
return p;
}
(async () => {
let onerror;
let errorArgs = null;
let pError = new Promise( (res, rej)=> {
onerror = (...args) => rej( args); // error handler rejects pError
window.addEventListener("error", onerror);
})
.catch( args => errorArgs = args); // Catch handler resolves with error args
// race between badPromise and global error
await Promise.race( [badPromise(), pError] );
window.removeEventListener("error", onerror); // remove global error handler
console.log("Made it here");
if( errorArgs) {
console.log(" but a global error occurred, arguments array: ", errorArgs);
}
})();
Issues
The code was written without caring what is passed to an global error handler added using addEventListener - you may get different arguments if you use window.onerror = errorHandler.
The promise race can be won by any error event that bubbles up to window in the example. It need not have been generated in the badPromise() call.
If multiple calls to badPromise are active concurrently, trapping global errors won't tell you which badPromise call errored.
Hence badPromise really is bad and needs to be handled with kid gloves. If you seriously cannot fix it you may need to ensure that you only ever have one call to it outstanding, and you are doing nothing else that might generate a global error at the same time. Whether this is possible in your case is not something I can comment on.
Alternative
A more generic alternative may be to start a timer before calling badPromise and use it to time out the pending state of the returned promise;
let timer;
let timeAllowed = 5000;
let timedOut = false;
let timeout = new Promise( res => timer = setTimeout(res, timeAllowed))
.then( timedOut = true);
await Promise.race( [badPromise(), timeout])
clearTimer( timer);
console.log( "timed out: %s", timedOut);
There may be a way to do this, but in your case I think you really want to use the reject function inside your Promise instead of throw. That's really what reject is for.
async function badPromise() {
const p = new Promise((res, reject) => {
delayTimer = setTimeout(() => {
console.log('running timeout code...');
if (1 > 0) {
reject('This is NOT caught!');
return;
}
res();
}, 1000);
});
return p;
}
(async () => {
try {
console.log('start async');
await badPromise();
console.log('Made it to the end'); // never gets here
} catch (e) {
console.error('Caught the problem...', e); // should work now
}
})();
Maybe not an answer to what you want, but you could use a pattern like this for setTimeout:
function testErrors() {
new Promise((resolve, reject) => {
setTimeout(() => resolve(), 1000);
}).then(() => {
throw Error("other bad error!");
}).catch(err => {
console.log("Catched", err);
})
}

How JavaScript promises work behind the scenes

I'm so much confused about what happens behind the scenes when promise is produced and consume. Please clarify my points and sorry for my weak English.
blank object is created with new keyword Promise constructor is
called and new keyword set the this of Promise constructor points to
the blank object this = blankobject.
Promise constructor receives callback (executor function) in argument
and calls the executor function.
executor function receives two callbacks (resolve,reject) as arguments
setTimeout gets called in the executor function and setTimeOut is
async code
async code goes to background and then Promise constructor returns
Promise object formerly blank object and Promise object reference
saved to myPromise.
a variable is created
What happens next ? When then method is called the code of then method goes to background? I imagine it goes to background and a variable is console.log // 10
After main code execution finishes, async code start setTimeout callback begins to execute and after execution finishes promise is fulfilled and resolved function returns value. How is this value stored in promise object and what happens in then method ?
let myPromise = new Promise (
(resolve, reject) => {
setTimeout(() => {
console.log(getIDs)
resolve(10);
}, 1500);
}
)
let a = 10
myPromise.then(val => {
console.log(val);
})
console.log(a)
The following is a simplified implementation of the built-in Promise class. catch and finally have not been implemented.
The function supplied to the Promise constructor is called the executor function, and is invoked immediately and synchronously.
Every promise has a method .then, enabling the chaining of promises.
Functions supplied to .then are always invoked asynchronously on a microtask (note use of queueMicrotask below).
Every time .then is called, a new promise is created and returned.
.then can be called more than once on the same promise, creating a multicast of the result of the promise, and a branching of the promise chain.
A promise can be in one of three states: pending, fulfilled, or rejected. State transitions are unidirectional: you cannot move from fulfilled or rejected, back to pending.
If a promise is resolved with another promise, then the two promise chains are joined and the outer promise takes on the status of the inner promise (which could be pending), until the inner promise resolves.
function Promise(executor) {
if (!executor) throw "Promise executor undefined"
let status = "pending", value, thenQ = []
const then = onFulfilled => {
let resolver
// This ensures control does not move to later promises
// until prior promises have been resolved.
const nextPromise = new Promise(resolve => (resolver = resolve))
// More than one "then" can be registered with each promise.
thenQ.push((...args) => resolver(onFulfilled(...args)))
return nextPromise
}
// We check if the result is a "thenable"; if so, we treat
// it as an inner promise, otherwise we simply fulfil with
// the result.
const resolve = result => result?.then ? result.then(fulfil) : fulfil(result)
// When a promise has been fulfilled, its "thens" can be run.
const fulfil = result => (status = "fulfilled", value = result, executeThens(value))
// "Thens" are run asynchronously, on a microtask.
const executeThens = value => queueMicrotask(() => thenQ.forEach(el => el(value)))
// The executor is run synchronously.
executor(resolve)
return {
then,
get status() { return status },
get value() { return value }
}
}
// Chaining
new Promise(resolve => {
console.log('Waiting for step 1...')
setTimeout(() => resolve("One, two..."), 1500)
})
.then(result => new Promise(resolve => {
console.log('Waiting for step 2...')
setTimeout(() => resolve(`${result}three, four`), 1500)
}))
.then(result => console.log(`Chaining result: ${result}.`))
// Branching
const p = new Promise(resolve => {
console.log('Waiting for step a...')
setTimeout(() => resolve("Alpha, Bravo..."), 1500)
})
p.then(result => new Promise(resolve => {
console.log('Waiting for step b1...')
setTimeout(() => resolve(`${result}Charlie, Delta`), 1500)
})).then(console.log)
p.then(result => {
console.log('Waiting for step b2...')
return `${result}Echo, Foxtrot`
}).then(console.log)
See also.
I'll go through your code in the order of execution.
At all times the value of this is whatever it was at the beginning. That's because you're only using arrow functions. But that's not relevant since you're not referencing this.
Main Code
let myPromise = new Promise(executor); creates a pending promise object. While creating the promise, the executor function will be executed.
setTimeout(callback, 1500); puts the callback function on some internal timer queue. The javascript engine promises to do its best to execute callback after (at least) 1500ms.
let a = 10; sets the variable a to 10.
myPromise.then(onFulfilled); creates another pending promise. It is linked to myPromise so that onFulfilled will be scheduled asynchronously when myPromise is fulfilled.
console.log(a); prints the value of a which is 10.
For the next 1500ms nothing happens. Then callback gets executed.
callback of setTimeout
console.log(getIDs); prints getIDs. From the name you can guess it's a function. So something like [Function: getIDs] will be printed.
resolve(10); fulfills myPromise and sets its result to 10. Since myPromised is now fulfilled, onFulfilled of anotherPromise gets scheduled asynchronously.
Now we have to wait for the call stack to process. After that, onFulfilled will be called.
onFulfilled of myPromise.then
console.log(val); prints the content of val. That is, the result of myPromise.

How to debug "TypeError: A promise cannot be resolved with itself"

In javascripts webconsole in my browser I read the Error message:
TypeError: A promise cannot be resolved with itself
which however does not furnish a source code line number or other reference, giving me trouble to even now where that type Error occurs. In all honesty debuging asynchronous things (as are par excellence Promises) turns out to be a challeng. Any idea how I can track down, what and where this TypeError happens?
What I have done is to be able to provoke the same error message
using this code:
var promise = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(promise);
},0);
});
But this does only confirm that such a thing as resolving
a promise with itself is indeed something that can occur
and which leads subsequenty to the promise being rejected
due to the occuring error;
console.log(promise);
// prints: Promise { <state>: "rejected" }
An ideal answer would provide some sort of guide (steps)
that help to show which part of the javascript source code
is actually causes the error.
A helpful answer could also list simply all the ways (other
than the one I have provided example code for) that can lead
to the very same error.
Updated
The only place in the source, where a new Promise(callback) occurs looks like this, and appears to me cannot cause a circular pattern.
function xhrGet(url){
return new Promise((resolve,reject)=>{
var xhr = new XMLHttpRequest();
xhr.open("GET",url);
xhr.responseType = "arraybuffer";
xhr.addEventListener("load",function(){
resolve(xhr.response);
});
xhr.addEventListener("error",function(er){
reject(er);
});
xhr.send();
});
}
It seems imho, unlikely to be the culprit.
here a screenshot of the console output of the debug window of Firefox(ver 68) showing that it sadly does not always show the line number
When resolve in a Promise constructor is called with a different Promise, the value that the constructed Promise resolves to will be what that Promise passed into resolve resolves to.
const existingProm = new Promise(resolve => {
setTimeout(() => {
console.log('existing promise resolving');
resolve();
}, 2000);
});
var secondPromise = new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log("second promise's resolve being called");
resolve(existingProm);
},1000);
});
secondPromise.then(() => {
console.log('second promise fulfilled');
});
As you can see in the above snippet, although secondPromise's resolve is called after 1000 ms, the secondPromise only actually resolves after 2000 ms, because existingProm (which resolves after 2000 ms) was passed to the resolve.
It's just like returning a Promise from inside a .then - the next .then will resolve to the value of what the returned Promise resolved to.
But if you pass the Promise you're constructing to the same resolve that's being used to construct the Promise, you've created a cycle - resolve(promise); will mean that the constructed Promise resolves only once promise resolves. But promise is that constructed Promise. The interpreter sees this circular situation and throws an error (by rejecting the Promise).
Any sort of circular Promise cycle can result in this error. For example, if prom1 calls its resolve with prom2, and prom2 calls its resolve with prom1:
const prom1 = new Promise(resolve => {
setTimeout(() => {
console.log('prom1 resolve being called');
resolve(prom1);
}, 1000);
});
const prom2 = new Promise(resolve => {
setTimeout(() => {
console.log('prom2 resolve being called');
resolve(prom2);
}, 1000);
});
prom1.catch((e) => console.log(e.message));
To debug an error like this, look at the Promise that generates the error, and examine what its resolve was called with, or what was returned from the .then that created the Promise. You have a circular chain in there somewhere. For example:
const prom1 = new Promise(resolve => {
setTimeout(() => {
console.log('prom1 resolve being called');
resolve(prom1);
}, 1000);
});
const prom2 = new Promise(resolve => {
setTimeout(() => {
console.log('prom2 resolve being called');
resolve(prom2);
}, 1000);
});
prom1.catch((e) => console.dir(e.stack));
On my machine, this shows the error as occurring
at https://stacksnippets.net/js:16:5
which corresponds to this line:
resolve(prom1);
which is indeed the source of the circular dependency for prom1.

Why can I await this code but not use .then?

Node.JS 10.15, serverless, lambdas, invoked locally
SAMPLE A) This Works:
export async function main(event) {
const marketName = marketIdToNameMap[event.marketId];
const marketObject = marketDirectory[marketName];
const marketClient = await marketObject.fetchClient();
const marketTime = await marketObject.getTime(marketClient);
console.log(marketTime);
}
SAMPLE B) and this works:
export function main(event) {
const marketName = marketIdToNameMap[event.marketId];
const marketObject = marketDirectory[marketName];
marketObject.fetchClient().then((marketClient)=>{
marketObject.getTime(marketClient).then((result) => {
console.log('<---------------> marker 1 <--------------->');
console.log(result);
});
});
}
SAMPLE C) but this does not:
export async function main(event) {
const marketName = marketIdToNameMap[event.marketId];
const marketObject = marketDirectory[marketName];
const marketClient = await marketObject.fetchClient();
console.log('<---------------> marker 1 <--------------->');
marketObject.getTime(marketClient).then((result) => {
console.log('<---------------> marker 22 <--------------->');
console.log(result);
});
}
the guts of getTime are for all examples:
function getTime(marketClient){
return new Promise((resolve, reject) => {
return marketClient.getTime((err, result) => {
if (err) {
reject(err);
}
resolve(result);
});
}).catch(err => {
throw err;
});
}
clearly, it seems to be an issue with mixing async/awaits with classic promise then-ables. I would expect SAMPLE C to work because getTime() is returning a promise. However, the code simply finishes silently, never hitting the second marker. I have to put the first marker there just to be sure any code is run at all. It feels like i should be able to mix async/await and thenables, but i must not be considering something here.
#adrian, nope
You're neither awaiting nor returning the promise from marketObject.getTime().then(), and this will result in that promise chain executing independently, the main function returning and the process closing. Remember.. then returns a promise too.
the solution is
await marketObject.getTime(marketClient).then(...
or
return marketObject.getTime(marketClient).then(...
either way chains the promise to the main function such that whatever executes it consistently waits for all promises to resolve (or reject).
I suspect Sample B works because the main is not async and Lambda will wait for the event-loop to complete. i.e. it will execute the promise chain even though main returned early.
https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html
If you don't use callback in your code, AWS Lambda will call it
implicitly and the return value is null. When the callback is called,
AWS Lambda continues the Lambda function invocation until the event
loop is empty.
... and I suspect if you return a Promise (as you do in Sample C) then Lambda will simply terminate the process immediately once it resolves, which is does because you don't await/return the .then() chain, and thus the floating promise chain you've created will not execute.

Categories

Resources