Are callbacks in node.js either always asynchronou

2019-04-12 06:27发布

I'm toying with making something in node.js, and I (like everyone else who's ever started learning node) have a question about the asynchronous nature of it. I searched around a bit, but couldn't find this specific question about it answered (maybe I just didn't search very well...), so here goes:

Are node.js callbacks, in general, guaranteed to be asynchronous if the documentation says so? If you make your own functions that take callbacks, should you design in such a way so that they're either always asynchronous or always synchronous? Or can they be sometimes synchronous, sometimes not?

As an example, lets say you wanted to load some data over the internet, and you created a function to load it. But the data doesn't change very often, so you decide to cache it for future calls. You could imagine that someone would write that function something like this:

function getData(callback) {
    if(dataIsCached) {
        callback(cachedData)
    } else {
        fetchDataFromNetwork(function (fetchedData) {
            dataIsCached = true;
            cachedData = fetchedData;
            callback(fetchedData);
        });
    }
}

Where fetchDataFromNetwork is a function that executes its callbacks asynchronously (this is slightly pseudo-codey, but I hope you understand what I mean).

This function will only fire asynchronously if the data isn't cached, if it is cached it just executes the callback directly. In that case, the asynchronous nature of the function is, after all, totally unnecessary.

Is this kind of thing discouraged? Should the second line of the function be setTimeout(function () {callback(cachedData)}), 0) instead, to guarantee that it fires asynchronously?

The reason I'm asking is that I saw some code a while back where the code inside the callback simply assumed that the rest of the function outside the callback had executed before the code inside the callback. I recoiled a little at that, thinking "but how do you know that the callback will fire asynchronously? what if it doesn't need to and executes synchronously? why would you ever assume that every callback is guaranteed to be asynchronous?"

Any clarification on this point would be much appreciated. Thanks!

4条回答
在下西门庆
2楼-- · 2019-04-12 07:05

You need to free yourself of the notion that something doesn't need to be asynchronous - in Node either something needs to be synchronous, or it should be asynchronous without another thought.

Async is the entire philosophy behind Node.js, and callbacks are by their very nature asynchronous (if they're designed properly), which allow Node to be non-blocking as it claims. This is what predicates the assumption that a callback will execute asynchronously - it's an intimate part of Node's design philosophy.

Should the second line of the function be setTimeout(function () {callback(cachedData)}), 0) instead, to guarantee that it fires asynchronously?

In Node we use process.nextTick() to ensure something runs Asynchronously, delaying it's function until the next tick of the event loop.

查看更多
Summer. ? 凉城
3楼-- · 2019-04-12 07:20

Personally I agree with your notion of never assuming asynchronous code execute after the function has returned (by definition asynchronous simply means you can't assume it's synchronous, not assume it's not synchronous).

But there have been a culture that have evolved around javascript that considers a function that can be either sync or async an anti-pattern. And it makes sense: if you can't predict when a code runs it's hard to reason about it.

So in general all the popular libraries avoid it. In general, it's quite safe to assume that async code never runs before end of script.

Unless you have a very special reason for it, don't write a function that can be both sync and async - it's considered an anti-pattern.

查看更多
叼着烟拽天下
4楼-- · 2019-04-12 07:25

You should never have a function that accepts a callback that is sometimes synchronous, this can cause you to run into tail recursion problems. Take the following example:

function doSynchronousWork (cb) {
    cb();
}
doSynchronousWork(function looper () {
   if (...somecondition...) {
       doSynchronousWork(looper);
   }
});

In the above example, you'll run into a max callstack exceeded error due to the callstack nesting too deeply. Forcing the synchronous function to run asynchronously fixes that problem by clearing the callstack before continuing the recursion.

function doSynchronousWork (cb) {
    process.nextTick(cb);
}
doSynchronousWork(function () {
   if (...somecondition...) {
       doSynchronousWork();
   }
});

Note that this tail recursion problem will be fixed eventually in the js engine

Do not confuse callbacks with iterator functions

查看更多
放荡不羁爱自由
5楼-- · 2019-04-12 07:28

Your assumptions are all correct.

Are node.js callbacks, in general, guaranteed to be asynchronous if the documentation says so?

Yes. Of course, there are functions with async callbacks and functions with sync callbacks, but none which do both.

If you make your own functions that take callbacks, should you design in such a way so that they're either always asynchronous or always synchronous?

Yes.

They could be sometimes synchronous, sometimes not, such as a cache? Is this kind of thing discouraged?

Yes. Very much d̲̭i̫̰̤̠͎͝ͅͅs͙̙̠c̙͖̗̜o͇̮̗̘͈̫ų̗͔̯ŕa҉̗͉͚͈͈̜g͕̳̱̗e҉̟̟̪͖̠̞ͅd͙͈͉̤̞̞̩.

Should the second line of the function be setTimeout(function () {callback(cachedData)}), 0) instead, to guarantee that it fires asynchronously?

Yes, that's a good idea, though in node you'd rather use setImmediate or process.nextTick instead of setTimeout.
Or use promises, which do guarantee asynchrony, so that you don't have to care about delaying things yourself.

I saw some code a while back where the code inside the callback simply assumed that the rest of the function outside the callback had executed before the code inside the callback. I recoiled a little at that

Yeah, understandable. Even if the API you're using guarantees asynchrony, it still would be better to write code so that it can be read in the order it would be executed. If possible, you should place the things that are executed immediately before the async callback (the exception proves the rule).

查看更多
登录 后发表回答