14

I write lots of modules which look like this:

function get(index, callback) {
    if (cache[index] === null) {
        request(index, callback); // Queries database to get data.
    } else {
        callback(cache[index]);
    }
}

Note: it's a bit simplified version of my actual code.

That callback is either called in the same execution or some time later. This means users of the module aren't sure which code is run first.

My observation is that such module reintroduces some problems of the multi-threading which was previously solved by JavaScript engine.

Question: should I use process.nextTick or ensure it's safe for the callback to be called outside the module?

Pijusn
  • 11,025
  • 7
  • 57
  • 76

2 Answers2

13

It depends entirely on what you do in the callback function. If you need to be sure the callback hasn't fired yet when get returns, you will need the process.nextTick flow; in many cases you don't care when the callback fires, so you don't need to delay its execution. It is impossible to give a definitive answer that will apply in all situations; it should be safe to always defer the callback to the next tick, but it will probably be a bit less efficient that way, so it is a tradeoff.

The only situation I can think of where you will need to defer the callback for the next tick is if you actually need to set something up for it after the call to get but before the call to callback. This is perhaps a rare situation that also might indicate a need for improvement in the actual control flow; you should not be rely at all on when exactly your callback is called, so whatever environment it uses should already be set up at the point where get is called.

There are situations in event-based control flow (as opposed to callback-based), where you might need to defer the actual event firing. For example:

function doSomething() {
    var emitter = new EventEmitter();
    cached = findCachedResultSomehow();
    if (cached) {
        process.nextTick(function() {
            emitter.emit('done', cached);
        });
    } else {
        asyncGetResult(function(result) {
            emitter.emit('done', result);
        });
    }
    return emitter;
}

In this case, you will need to defer the emit in the case of a cached value, because otherwise the event will be emitted before the caller of doSomething has had the chance to attach a listener. You don't generally have this consideration when using callbacks.

Kuf
  • 17,318
  • 6
  • 67
  • 91
lanzz
  • 42,060
  • 10
  • 89
  • 98
  • So, if I am about to write a reusable module (I may not be the only one using it), should I ensure it's safe and use `process.nextTick`? – Pijusn Oct 26 '12 at 10:33
  • What situation do you foresee where you will need to do something in between the call to `get` and the `callback` getting invoked? It really depends on your specific situation. – lanzz Oct 26 '12 at 10:38
  • Well, the question is pretty general as I don't really know what is going to be done in the future (have no problems so far). – Pijusn Oct 26 '12 at 10:42
  • You said in your question that you have actually observed problems with instant callbacks. If you haven't had any problems, you might be trying to fix a problem that does not exist. – lanzz Oct 26 '12 at 10:44
  • I meant I see potential problems in the future as I may forget that callback may be called instantly or someone else may not know and write code that is not multi-thread safe. It's actually possible that there may be no problems in the future with that. – Pijusn Oct 26 '12 at 10:54
  • 4
    There is no multithreading involved at any point. The only difference is that the order of execution might be either `get > (code after get) > callback` or `get > callback > (code after get)`, but code after `get` should not rely on being run before or after `get`'s callback in the first place. – lanzz Oct 26 '12 at 11:25
8

http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony

if you're doing callbacks internally, do whichever is suitable

if you're creating a module used by other people, asynchronous callbacks should always be asynchronous.

Jonathan Ong
  • 19,927
  • 17
  • 79
  • 118