console.log() async or sync?
Asked Answered
J

4

133

I am currently reading Async Javascript by Trevor Burnham. This has been a great book so far.

He talks about this snippet and console.log being 'async' in the Safari and Chrome console. Unfortunately I can't replicate this. Here is the code:

var obj = {}; 
console.log(obj); 
obj.foo = 'bar';
// my outcome: Object{}; 'bar';
// The book outcome: {foo:bar};

If this was async, I would anticipate the outcome to be the books outcome. console.log() is put in the event queue until all code is executed, then it is ran and it would have the bar property.

It appears though it is running synchronously.

Am I running this code wrong? Is console.log actually async?

Jacobah answered 30/4, 2014 at 15:25 Comment(6)
@thefourtheye: No, so I should probably just delete my comment.Shy
I've seen it happen in Chrome. If you console.log a simple object and then immediately change something in the object, the console.log() does not always show the former value. The work-around if this happens to you is to convert whatever you're trying to console.log() to a string which is immutable so not subject to this issue. So, from experience console.log() has some async issues probably related to marshaling data across process boundaries. This is not the intended behavior, but is some side effect of how console.log() works internally (I'd personally consider it a bug).Sculpturesque
See also console.log() shows the changed value of a variable before the value actually changesVolva
See also Can't access object property, even though it exists. Returns undefinedVolva
@bergi it just took me 10 minutes to find this dupe (although I knew the exact name), probably because it is dupeclosed. Couldn't we just swap the duplicate, so that the other one will be the dupe ... ?Tessie
@JonasWilms I have now reopened this question (see history). I don't think they're duplicates of each other, I use Is Chrome's JavaScript console lazy about evaluating arrays? as the canonical target for problems specifically involving an array.Volva
V
157

console.log is not standardized, so the behavior is rather undefined, and can be changed easily from release to release of the developer tools. Your book is likely to be outdated, as might my answer soon.

To our code, it does not make any difference whether console.log is async or not, it does not provide any kind of callback or so; and the values you pass are always referenced and computed at the time you call the function.

We don't really know what happens then (OK, we could, since Firebug, Chrome Devtools and Opera Dragonfly are all open source). The console will need to store the logged values somewhere, and it will display them on the screen. The rendering will happen asynchronously for sure (being throttled to rate-limit updates), as will future interactions with the logged objects in the console (like expanding object properties).

So the console might either clone (serialize) the mutable objects that you did log, or it will store references to them. The first one doesn't work well with deep/large objects. Also, at least the initial rendering in the console will probably show the "current" state of the object, i.e. the one when it got logged - in your example you see Object {}.

However, when you expand the object to inspect its properties further, it is likely that the console will have only stored a reference to your object and its properties, and displaying them now will then show their current (already mutated) state. If you click on the +, you should be able to see the bar property in your example.

Here's a screenshot that was posted in the bug report to explain their "fix":

So, some values might be referenced long after they have been logged, and the evaluation of these is rather lazy ("when needed"). The most famous example of this discrepancy is handled in the question Is Chrome's JavaScript console lazy about evaluating arrays?

A workaround is to make sure to log serialized snapshots of your objects always, e.g. by doing console.log(JSON.stringify(obj)). This will work for non-circular and rather small objects only, though. See also How can I change the default behavior of console.log in Safari?.

The better solution is to use breakpoints for debugging, where the execution completely stops and you can inspect the current values at each point. Use logging only with serialisable and immutable data.

Volva answered 30/4, 2014 at 15:50 Comment(12)
i had the same issue with console.log not being async. using JSON.stringify fixed it for meTelangiectasis
As of 2019, can we say that console.log is still asynchronous in Chrome as it was 8 years old (see #7389569), the only thing that changes is that now Chrome outputs a snapshot of the reference object at the time you call console.log (if you expand the logged object, you will see its final properties and values after the mutation operations you made after console.log), or is console.log indeed synchronous?Kleptomania
@Kleptomania Yes, this behaviour is unlikely to be changed due to the reasons laid out in my answer. It's not a bug, it's just how an interactive debugger/inspector works.Volva
If you use JSON.parse(JSON.stringify(obj)) as also mentioned in the comment here you get a snapshot in object form, instead of a string.Jasun
TL;DR.way way way TLPastoralist
@RickO'Shea It even has pictures!Volva
@Jasun JSON.stringify doesn't serialize functions, symbols and undefined. So, you may loose crucial info while debugging.Saval
I can confirm that in latest version of Node as of today, v17.6.0 in March 2022, console.log is asynchronous. I put console.log and process.stdout.write statements right next to each other in the root script and inside functions. All the process.std.writes printed first, and all the console.logs printed last, not showing the state of the printed object at the time of the call. So I used process.stdout.write(`${JSON.stringify(object, null, 2)}\n`)Virga
@Virga The nodejs console does synchronously call util.format, so it will print the object state as of the time of the call. But yes, writing into stdout itself is weird, "The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information." as the docs put it.Volva
@Virga It's possible that the direct process.stdout.write call is flushed first, before the console.log call that happened earlier, but console.log in nodejs does not save the reference to the object - see the code I linked. It does immediately serialise it and write the string into the stream. You may get different results if you have a debugger attached.Volva
YOU ARE RIGHT! My bad. Not a debugger, but code in the test framework I am using is capturing the args to console.log calls, printing them later only on test fails. It holds onto references to the log args, rather than printing them to strings immediately. I'm going to submit a patch. Thanks for setting me straight. I'm going to delete my intermediate comment to reduce the noise here.Virga
@Virga wow that's confusing… I'd suggest to file a bug report with that framework. Keep up the comments, I think they're still valuable to others.Volva
N
3

This isn't really an answer to the question, but it might be handy to someone who stumbled on this post, and it was too long to put in a comment:

window.console.logSync = (...args) => {
  try {
    args = args.map((arg) => JSON.parse(JSON.stringify(arg)));
    console.log(...args);
  } catch (error) {
    console.log('Error trying to console.logSync()', ...args);
  }
};

This creates a pseudo-synchronous version of console.log, but with the same caveats as mentioned in the accepted answer.

Since it seems like, at the moment, most browsers' console.log's are asynchronous in some manner, you may want to use a function like this in certain scenarios.

Numerary answered 12/12, 2019 at 17:0 Comment(1)
JSON.stringify doesn't serialize functions, symbols and undefined. So you'll loose some info with this approach.Saval
O
1

When using console.log:

a = {}; a.a=1;console.log(a);a.b=function(){};
// without b
a = {}; a.a=1;a.a1=1;a.a2=1;a.a3=1;a.a4=1;a.a5=1;a.a6=1;a.a7=1;a.a8=1;console.log(a);a.b=function(){};
// with b, maybe
a = {}; a.a=function(){};console.log(a);a.b=function(){};
// with b

in the first situation the object is simple enough, so console can 'stringify' it then present to you; but in the other situations, a is too 'complicated' to 'stringify' so console will show you the in memory object instead, and yes, when you look at it b has already be attached to a.

Ornstead answered 30/9, 2014 at 7:45 Comment(1)
I know this question is 3 years old but right now I'm running in the same problem - serializing the object doesn't work for me because it's too complicated. I'm catching an event trying to access it's data but somehow it has no data in the code but in console.log it has data.Dunc
A
-1
const a = () => {
    console.log(1);
    const b = async () => {
        console.log(2);
    }

    const c = async () => {
        console.log(3);
        const d = async () => {
            console.log(4);
        }
        await d();
        console.log(5);
    }
    console.log(6);
    c();
    b();
    console.log(7);
}

a();

Pay attention to the number 7. In this example console.log() is async

Altazimuth answered 1/4 at 9:51 Comment(1)
This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From ReviewRudolf

© 2022 - 2024 — McMap. All rights reserved.