The other day I came across the following peace of code:
let promise = Promise.reject(new Error('Promise Failed!')); // 1
setTimeout(() => promise.catch(err => alert('caught')), 1000); // 2
// 3
So I was quite surprised to find out the error was caught in the handler registered one second after the error had occurred.
Now, let me explain step by step the way I perceive this, so you could correct me and tell me where I'm wrong:
During the current macrotask, which is executing this whole script, a promise rejects (1) and since there is no registered rejection handler the control sequentially moves on to the next line.
We call setTimeout passing it a callback function where rejection handler is to be registered (2). But this callback will be scheduled only after a second delay. Furthermore, it will be executed within a separate task.
The execution is on line 3. The current macrotask is done, both stack and task queue are empty. All that's left is to wait for a given delay of 1 second.
One second timeout is complete. Our callback gets to the task queue and right away it is being pushed to the stack which effectively runs it.
During the execution of the callback function, which is part of a new task, .catch handler is registered for the promise rejected one second ago, which took place during the previous and already finished task. Nevertheless, it successfully catches the error.
Does this mean that all this time the error had been somewhere in memory waiting for a chance that maybe .catch handler would be registered later? But what if it had never happened and the number of rejected promises had been a lot more? Will unhandled errors remain to 'hang' in memory waiting for its handler to be registered?
Does this mean that all this time the error had been somewhere in memory waiting for a chance that maybe .catch handler would be registered later?
Yes, it is stored in the promise object. Notice that promises are not just a notification mechanism, they are meant to represent the result of a one-off asynchronous task. The fulfillment value or rejection reason (in your case, the Error instance) and the resolution state are stored on the promise when it settles.
The main idea behind this is that a .then or .catch handler will always be called with the result value, regardless whether the .then() call happens before or after the settling of the promise. This also allows multiple handlers.
Will unhandled errors remain to 'hang' in memory waiting for its handler to be registered?
If you didn't have the setTimeout, the let promise variable and with it the promise object and the error object would have been garbage-collected immediately.
to answer your question directly:
Does this mean that all this time the error had been somewhere in memory waiting for a chance that maybe .catch handler would be registered later?
Yes, we have a WeakMap of pending rejections that keeps track of promises that were rejected but not synchronously handled. Chrome does something similar (I can link to it if you want).
But what if it had never happened and the number of rejected promises had been a lot more?
The assumption in the design of these features is that rejections are pretty rare and are for exceptional cases - so the rejection path is kind of slow. But yes, you could theoretically create a lot of "pending" rejections.
Will unhandled errors remain to 'hang' in memory waiting for its handler to be registered?
We only wait for a microtick - so the flow is:
You create a rejected promise
It has no handler, so it gets put in the WeakMap
All microtasks are run (process.nextTick/Promise.resolve.then)
If it is still rejected, it is an unhandled rejection and it gets logged to the screen / the event is fired.
That is, the rejection is not kept "in memory" for 1000ms (the timer duration) but just for as long as it takes for microtasks to run.
Related
If I enter the following into the console, no error is reported to the console
let p = new Promise(function(resolve, reject) {
console.log('started')
reject('immediately reject')
})
console.log('do some other work')
p.catch(function(error) {
console.log('error caught')
})
// Outputs:
// do some other work
// error caught
But if I remove the call to catch an uncaught error is shown. I can even type in the first half, hit enter, see the error, then add the catch and the error goes away. This seems weird to me: how can JavaScript know that a promise will eventually be caught? How would this affect control flow of an application if there's always a chance a promise could later be caught?
I understand promises are typically for asynchronous code, but one could imagine a promise which may return immediately with a validation error or something.
I am aware of try/catch, I'm just trying to understand how this works.
Running in Microsoft Edge 91.0.864.59 (64-bit)
If you're just talking about a message you see in the console that then disappears, then this is just something that occurs in a specific Javascript environment where it decides after-the-fact to rescind/remove a debug message in the console. It does not affect the running of your code in any way. Try three different browsers and you will see different behaviors in each because what shows in the console and when it shows there is up to the implementor of the engine/console, not something that is standardized or something that affects the outcome of your code.
Beyond that, let's discuss issues related to the timing of when a .catch() handler is added. The rejection of your promise is not processed synchronously. The promise state is changed immediately internal to the promise, but it does not synchronously call any .catch() handlers. Instead, a job is inserted into the promise job queue and only when the current chunk of Javascript that is executing is finished and returns control back to the event loop does the promise job get to do its work.
So, in your code, the .catch() handler is added in the current chunk of Javascript execution BEFORE the promise tries to call its catch handlers. Thus, when the promise job that contains the rejection does actually get processed, it is not an uncaught promise because the .catch() handler is already in place.
FYI, typing code into the console and executing it there will not necessarily offer the same timing (and thus errors) as running code in a real script. My comments above are about what happens if you run your whole script at once. I always evaluate asynchronous code in a real execution environment running a complete script, not by typing code into a console and running it pieces at a time.
I can even type in the first half, hit enter, see the error, then add the catch and the error goes away.
That's just a weirdness that occurs in the console when you run pieces of code, but not the whole script. That is not representative of running the whole code in a script.
This seems weird to me: how can JavaScript know that a promise will eventually be caught?
It doesn't. It evaluates whether there's a .catch() handler when the rejection is actually processed (which is via the Promise job queue).
How would this affect control flow of an application if there's always a chance a promise could later be caught?
It's not really an issue because the .catch() handler just needs to be in place before control returns to the event loop when the promise is actually rejected. And, that is usually how code is written so this isn't an issue. You create the promise (or call a function that returns a promise), then you add handlers to it - all in one body of code.
I understand promises are typically for asynchronous code, but one could imagine a promise which may return immediately with a validation error or something.
Neither .then() or .catch() handlers are ever called synchronously, even if the promise is resolved or rejected synchronously. They are always called via the Promise job queue which is always after the current synchronously running Javascript finishes executing and returns control back to the event loop.
Inside of a then() function, if I didn't return a promise but calling the function directly.
doSomething().then(function () {
doSomethingElse(); //I know I should return doSomethingElse()
}).then(finalHandler);
I know doSomethingElse & finalHandler will run in parallel then instead of running sequentially. But I am still not sure why is that exactly?
doSomething
|-----------------|
doSomethingElse(undefined)
|------------------|
finalHandler(undefined)
|------------------|
When you run code in a .then() handler, you get the following design choices:
1. Return nothing. That leaves the return value undefined and that is a signal to the parent promise that there is no additional asynchronous operation to wait for here so the promise chain can continue running the next steps in the chain.
2. Return a promise. This tells the parent promise that you want to "insert" a promise into the chain and the following .then() handlers should not be called until this promise is resolved. The chain will essentially wait for this promise. If this new promise is ultimately resolved, the next .then() handler will get called. If this new promise is ultimately rejected, the next .catch() handler will get called.
3. Throw an exception. This tells the parent promise that the operation in the .then() handler failed and the parent promise chain immediately becomes rejected and the next .catch() handler will get called.
So, in your case, if doSomethingElse() is an asynchronous operation and you don't return a promise that is connected with that asynchronous operation, then you've just "branched" your promise chain into two separate chains. The main parent chain will continue calling the next .then() handler because you returned nothing. Meanwhile, your doSomethingElse() function is essentially its own parallel promise chain. It could even have it's own .then() handlers as in:
doSomethingElse().then(...).then(...).catch(...)
That would just be a completely separate promise chain that would have no connection at all to the other promise chain except for the timing of when this other promise chain was started. Once it starts, it runs independently from the other chain. This is typically referred to as "branching" in promise terminology. You branch into a new chain. The two run separate form one another. If both branches use asynchronous operations (which they presumably do), those asynchronous operations would be interleaved and both in flight at the same time. The timing of when they both finished would be completely indeterminate (since they have no programmatic relationship in their timing).
Branching to a completely independent promise chain like this is usually a programming error and some promise implementations may report a likely programming error in the console. The reason this is usually an error is there is no way for anyone outside this code to have any way to monitor or catch errors in the branched and independent promise. And promises without error handling are bad. They eat errors silently.
There are certain cases where you legitimately don't change your program behavior if an error happens. Often times when you're closing a file at the end of a long sequence or even just trying to close files after errors have occurred, you just want to make your best efforts to close the file and you don't really have anything more useful to do if the close fails (except perhaps log the failure) so there's no particular reason to try to propagate back that type of failure. But, this should only be done in a very thoughtful way. 99.9999% of the time, errors should be propagated back to the caller and creating a new branched and independent promise chain like this does not propagate its errors back anywhere so it's usually not the right coding strategy.
The function does not need to return a Promise. If nothing was explicitly returned, by default undefined is returned. Functions in Javascript work like that. See the example
function doSomething() {
}
console.log(doSomething());
When you return a Promise from the function in the then chains, then will work only if the returned Promise is resolved. If an exception was occurred, the catch function will work if the last exists.
So actually your code is like
doSomething().then(function () {
doSomethingElse();
return undefined;
}).then(finalHandler); `undefined` is passed into the `finalHandler` function
What about the parallel, they will work not in parallel, but sequentially if the code is then(...).then(...). These then work sequentially. But if your doSomethingElse also returns a Promise, it will have its own sequence of chain. It's flow is independent from the doSomething flow.
I've observed that in the following code:
setTimeout(function(){console.log('setTimeout')});
Promise.resolve(1).then(function(){console.log('promise resolve')})
No matter how many times I execute this, the promise callback always logs before the setTimeout.
My understanding is that both callbacks are scheduled to be executed to the next tick, and I don't really understand what is going on that makes the promise always take precendence over the timeout.
Promise.resolve schedules a microtask and the setTimeout schedules a macrotask. And the microtasks are executed before running the next macrotask.
Short answer Promises have better priority than setTimeout callback function in event loop stack(or how i understand it).
Long answer watch this video. Very helpful. Hope this helps.
https://www.youtube.com/watch?v=8aGhZQkoFbQ
Thanks #MickJuice for new and updated video for event loop.
https://www.youtube.com/watch?v=cCOL7MC4Pl0
setTimeout() has a minimum delay of 4ms, so even though you didn't specify a delay in your code the timeout will still be delayed at least 4ms. Your promise's .then() is called in the meantime.
Timeouts and Promises both serve to execute code in an asynchronous way but with differences characteristics and purposes:
setTimeout
- Delays the execution of a function by specific time duration.
- Does not block the rest of the code execution (asynchronous behavior)
- They create Macrotask (browser internal operation)
Promises
- They are a wrapper to allow asynchronous execution of code(Eg: an ajax call). (Does not depend on specific time duration)
- They are especially useful to chain different async calls.
- Does not block the rest of the code execution (asynchronous behavior) at less you are using the await operator.
- They create Microtask (browser internal operation), which have priority over the Macrotask.
Recommendation
Use setTimeout when you want to delay a function execution some specific time duration and not block the rest of the code execution in the process
Use Promises:
When you want to execute some async code and to avoid the “callback hell” (yes because you can make asynchronous ajax calls without Promises but the syntax is less clear and more prone to errors)
This has to do with the event loop as defined in the Web Spec. The browser has multiple task queues for multiple types of tasks (e.g. timer tasks created through setTimeout), as well as a microtask queue (where Promise settlement gets pushed to). Whenever the browser finishes executing a task, it empties the microtask queue and executes all tasks in it, before continuing with a task from another task queue.
Therefore after the code executes (which is a task), the Promise settlement is inside of the microtask queue, and the timer task might already be inside a task queue¹. The microtask queue gets emptied and the Promise resolves. Then somewhen the timer task runs.
¹ Browsers might choose to increase timeouts a bit, and they do. A timeout will never run after 0ms in most browsers.
Timeouts and Promises serve different purposes.
setTimeout delays the execution of the code block by a specific time duration. Promises are an interface to allow async execution of code.
A promise allows code to continue executing while you wait for another action to complete. Usually this is a network call. So anything in your then() call will be executed once the network call (or whatever the promise is waiting for) is completed. The time difference between the start of the promise and the resolution of the promise entirely depends on what the promise is executing, and can change with every execution.
The reason the promise is executing before your timeout is that the promise isn't actually waiting for anything so it resolved right away.
I don't understand why resoved Promise delay .then() argument call?
example:
var myPromise = Promise.resolve();
console.log(myPromise);
myPromise.then(()=>console.log('a'));
console.log('b');
console return:
> Promise { <state>: "fulfilled", <value>: undefined }
> "b"
> "a"
If myPromise is fulfilled, why .then() don't call imediatly resolve function?
Because, by specification, promises call their resolve handler AFTER the current thread of execution unwinds and finishes back to "platform code". That guarantees that they are always called asynchronously.
So, thus you see the console.log('b') first as that thread of execution finishes and then the resolve handler is called where you see the console.log('a').
From the Promises/A+ specification:
2.2.4 onFulfilled or onRejected must not be called until the execution
context stack contains only platform code. [3.1].
And, here's note [3.1]:
Here “platform code” means engine, environment, and promise
implementation code. In practice, this requirement ensures that
onFulfilled and onRejected execute asynchronously, after the event
loop turn in which then is called, and with a fresh stack. This can be
implemented with either a “macro-task” mechanism such as setTimeout or
setImmediate, or with a “micro-task” mechanism such as
MutationObserver or process.nextTick. Since the promise implementation
is considered platform code, it may itself contain a task-scheduling
queue or “trampoline” in which the handlers are called.
This is done to provide consistent execution order so no matter when the promise is resolved (synchronously or asynchronously), the then() handlers are always called in the same timing relative to other code. Since many promises are resolved asynchronously, the only way to make a given promise consistent no matter how it is resolved is to make them always call their .then() handlers asynchronously.
jfriend00's answer is correct. Allow me to elaborate on why. Let's say I got myPromise from somewhere. I don't know it's just a Promise.resolve, it might resolve asynchronously, and it might not.
myPromise.then(function(){
console.log("a");
});
console.log("b");
If the asynchronousity guarantee didn't exist - that would sometimes log a b and sometimes b a. This is a race condition, and is a terrible thing to have. Promises are not susceptible to this problem by design - and execution order is always guaranteed in new promises implementations and native promises in particular.
The actual implementation of running new jobs is via Job Queues. then enqueues a job to the job queue in the handler, and job queues are run after code is ready - this is specified here.
In addition to then(), Q.js also has a done(). done() is usually called at the end of a promise chain, like this:
promise
.then(callback)
.then(callback)
.done(callback);
This will catch any rejections that were not handled by the previous then()s, and it will handle any exceptions raised in then()'s callbacks.
Is there something similar in when.js? How do you handle exceptions raised in callbacks? And what if you never register a rejection handler?
It looks like when now has .done() as well as .catch() and .finally().
See https://github.com/cujojs/when/blob/master/docs/api.md#extended-promise-api
As far as I know, there is no done in when.js. Indeed, if you read the last paragraph dedicated to debugging, there is a mention on a tool called monitor, which:
[...] monitors promise state transitions and then takes action, such as logging to the console, when certain criteria are met, such as when a promise has been rejected but has no onRejected handlers attached to it, and thus the rejection would have been silent.
There's no done in when.js.
I advice to request it in their issue tracker, and for a meantime use other library that provides done.