When to use callbacks? - node.js

I don't quite understand the use of callbacks in node.js. I understand that if you have something like
result = db.execute(query);
doSomething(result);
you should make doSomething a callback because doSomething would get executed before the result is ready. This makes sense because the db operation can be expensive.
Now let's say I have something like
result = calculate(x,y)
doSomething(result)
where calculate is not expensive (i.e. no reading from database or I/O), should I still be using a callback? How can I tell if my function would complete before or after the next line would get executed?
Thanks

In short, your function needs to accept a callback parameter if your function is calling asynchronous functions (e.g. invoking I/O operations or database calls) so that the results of those calls can be provided to the caller of your function. If your function is just making synchronous calls then your function is also synchronous and you don't need a callback parameter (as in the case of your second example).

Related

Considering node.js asynchronous architecture, why doesn't push throw error when executed after array init function calls?

I'm fairly new to Node.js and learning about the history of callbacks, Promises, and async/await. So far I have written some code that I expected to throw an error, but it works fine. I suppose I should be happy with that, but I'd like to fully understand when async/await needs to be used.
Here is my code:
const rooms = {};
function initRoom(roomId) {
if (!rooms[roomId]) {
rooms[roomId] = {};
rooms[roomId].users = [];
rooms[roomId].messages = [];
}
}
initRoom('A');
initRoom('B');
rooms.A.users.push(1);
rooms.A.users.push(2);
rooms.B.users.push(3);
console.log(rooms);
I expected the first push() to throw an error, assuming that it would execute in the stack before the initRoom() function calls completed. Why doesn't it throw an error?
There is nothing in initRoom() that is asynchronous. It's all synchronous code.
The "asynchronous architecture" you refer to has to do with specific library operations in nodejs that have an asynchronous design. These would be things such as disk operations or network operations. Those all have an underlying native code implementation that allows them to be asynchronous.
Nothing in your initRoom() function is asynchronous or calls anything asynchronous. Therefore, it is entirely synchronous and each line of code executes sequentially.
but I'd like to fully understand when async/await needs to be used.
You use promises and optionally async/await when you have operations that are actually asynchronous (they execute in the background and notify of completion later). You would not typically use them with purely synchronous code because they would only complicate the implementation and do not add any value if all the code is already synchronous.
I expected the first push() to throw an error, assuming that it would execute in the stack before the initRoom() function calls completed.
initRoom() is not asynchronous and contains no asynchronous code. When you call it, it runs each line in it sequentially before it returns and allows the next line of code after calling initRoom() to execute.

How can I stop async/await from bubbling up in functions?

Lets say I have a function A that uses a function B which uses C, etc:
A -> B -> C -> D and E
Now assume that function D has to use async/await. This means I have to use async/await to the call of function C and then to the call of function B and so on. I understand that this is because they depend on each other and if one of them is waiting for a function to resolve, then transitively, they all have to. What alternatives can I do to make this cleaner?
There is a way to do this, but you'll loose the benefits of async-await.
One of the reason for async-await, is, that if your thread has to wait for another process to complete, like a read or write to the hard-disk, a database query, or fetching some internet information, your thread might do some other useful stuff instead of just waiting idly for this other process to complete.
This is done by using the keyword await. Once your thread sees the await. The thread doesn't really wait idly. Instead, it remembers the context (think of variable values, parts of the call stack etc) and goes up the call stack to see if the caller is not awaiting. If not, it starts executing these statements until it sees an await. It goes up the call stack again to see if the caller is not awaiting, etc.
Once this other process is completed the thread (or maybe another thread from the thread pool that acts as if it is the original thread) continues with the statements after the await until the procedure is finished, or until it sees another await.
To be able to do this, your procedure must know, that when it sees an await, the context needs to be saved and the thread must act like described above. That is why you declare a method async.
That is why typical async functions are functions that order other processes to do something lengthy: disk access, database access, internet communications. Those are typical functions where you'll find a ReadAsync / WriteAsync, next to the standard Read / Write functions. You'll also find them in classes that are typically designed to call these processes, like StreamReaders, TextWriters etc.
If you have a non-async class that calls an async function and waits until the async function completes before returning, the go-up-the-call-stack-to-see-if-the-caller-is-not-awaiting stops here: your program acts as if it is not using async-await.
Almost!
If you start an awaitable task, without waiting for it to finish, and do something else before you wait for the result, then this something else is executed instead of the idly wait, that the thread would have done if you would have used the non-async version.
How to call async function from non-async function
ICollection<string> ReadData(...)
{
// call the async function, don't await yet, you'll have far more interesting things to do
var taskReadLines = myReader.ReadLinesAsync(...);
DoSomethingInteresting();
// now you need the data from the read task.
// However, because this method is not async, you can't await.
// This Wait will really be an idle wait.
taskReadLines.Wait();
ICollection<string> readLines= taskRead.Result;
return readLines();
}
Your callers won't benefit from async-await, however your thread will be able to do something interesting while the lines have not been read yet.

Is it safe to skip calling callback if no action needed in nodejs

scenario 1
function a(callback){
console.log("not calling callback");
}
a(function(callback_res){
console.log("callback_res", callback_res);
});
scenario 2
function a(callback){
console.log("calling callback");
callback(true);
}
a(function(callback_res){
console.log("callback_res", callback_res);
});
will function a be waiting for callback and will not terminate in scenario 1? However program gets terminated in both scenario.
The problem is not safety but intention. If a function accepts a callback, it's expected that it will be called at some point. If it ignores the argument it accepts, the signature is misleading.
This is a bad practice because function signature gives false impression about how a function works.
It also may cause parameter is unused warning in linters.
will function a be waiting for callback and will not terminate in scenario 1?
The function doesn't contain asynchronous code and won't wait for anything. The fact that callbacks are commonly used in asynchronous control flow doesn't mean that they are asynchronous per se.
will function a be waiting for callback and will not terminate in scenario 1?
No. There is nothing in the code you show that waits for a callback to be called.
Passing a callback to a function is just like passing an integer to a function. The function is free to use it or not and it doesn't mean anything more than that to the interpreter. the JS interpreter has no special logic to "wait for a passed callback to get called". That has no effect one way or the other on when the program terminates. It's just a function argument that the called function can decide whether to use or ignore.
As another example, it used to be common to pass two callbacks to a function, one was called upon success and one was called upon error:
function someFunc(successFn, errorFn) {
// do some operation and then call either successFn or errorFn
}
In this case, it was pretty clear that one of these was going to get called and the other was not. There's no need (from the JS interpreter's point of view) to call a passed callback. That's purely the prerogative of the logic of your code.
Now, it would not be a good practice to design a function that shows a callback in the calling signature and then never, ever call that callback. That's just plain wasteful and a misleading design. There are many cases of callbacks that are sometimes called and sometimes not depending upon circumstances. Array.prototype.forEach is one such example. If you call array.forEach(fn) on an empty array, the callback is never called. But, of course, if you call it on a non-empty array, it is called.
If your function carries out asynchronous operations and the point of the callback is to communicate when the asynchronous operation is done and whether it concluded with an error or a value, then it would generally be bad form to have code paths that would never call the callback because it would be natural for a caller to assume the callback is doing to get called eventually. I can imagine there might be some exceptions to this, but they better be documented really well with the doc/comments for the function.
For asynchronous operations, your question reminds me somewhat of this: Do never resolved promises cause memory leak? which might be useful to read.

Too many callbacks issue

I know that writing async functions is recommended in nodejs. However, I feel it's not so nescessary to write some non IO events asynchronously. My code can get less convenient. For example:
//sync
function now(){
return new Date().getTime();
}
console.log(now());
//async
function now(callback){
callback(new Date().getTime());
}
now(function(time){
console.log(time);
});
Does sync method block CPU in this case? Is this remarkable enough that I should use async instead?
Async style is necessary if the method being called can block for a long time waiting for IO. As the node.js event loop is single-threaded you want to yield to the event loop during an IO. If you didn't do this there could be only one IO outstanding at each point in time. That would lead to total non-scalability.
Using callbacks for CPU work accomplishes nothing. It does not unblock the event loop. In fact, for CPU work it is not possible to unblock the event loop. The CPU must be occupied for a certain amount of time and that is unavoidable. (Disregarding things like web workers here).
Callbacks are nothing good. You use them when you have to. They are a necessary consequence of the node.js event loop IO model.
That said, if you later plan on introducing IO into now you might eagerly use a callback style even if not strictly necessary. Changing from synchronous calls to callback-based calls later can be time-consuming because the callback style is viral.
By adding a callback to a function's signature, the code communicates that something asynchronous might happen in this function and the function will call the callback with an error and/or result object.
In case a function does nothing asynchronous and does not involve conditions where a (non programming) error may occur don't use a callback function signature but simply return the computation result.
Functions with callbacks are not very convenient to handle by the caller so avoid callbacks until you really need them.

Trying to understand generators / yield in node.js - what executes the asynchronous function?

Node.js now has generators.
My understanding is that generators can be used to write code that appears to be much more linear and avoids callback hell and pyramid of doom style coding.
So to this point, my understanding is that inside a generator, code executes until it reaches a "yield" statement. Execution of the generator function suspends at this point. The yield statement specifies a return value which may be a function. Typically this would be a blocking I/O function - one that would normally need to be executed asynchronously.
The yield's return function is returned to whatever called the generator.
My question is, what happens at this point? What exactly executes the blocking I/O function that the yield returned?
Is it correct that to write generator/yield code that appears to be linear, there needs to be a specific sort of function that is calling the generator, a function that loops through the generator and executes each asynch function returned by the yield and returns the result of the asynch function back into the generator?
It's still not clear to me exactly how the asynch function returned by the yield is executed. If it is executed by the function that calls the generator, is it executed asynchronously? I'm guessing so because to do otherwise would result in blocking behaviour.
To summarise my questions:
To write "linear" asynch code with generators, is it necessary for there to be a calling function that iterates over the generator, executing yielded functions as callbacks and returning the result of the callback back into the generator?
If the answer to question 1 is yes, the exactly how are the yielded functions executed - asynchronously?
Can anyone offer a better overview/summary of how the whole process works?
When writing async code with generators you are dealing with two types of functions:
normal functions declared with function. These functions cannot yield. You cannot write async code in sync style with them because they run to completion; you can only handle asynchronous completion through callbacks (unless you invoke extra power like the node-fibers library or a code transform).
generator functions declared with function*. These functions can yield. You can write async code in sync style inside them because they can yield. But you need a companion function that creates the generator, handles the callbacks and resumes the generator with a next call every time a callback fires.
There are several libraries that implement companion functions. In most of these libraries, the companion function handles a single function* at a time and you have to put a wrapper around every function* in your code. The galaxy library (that I wrote) is a bit special because it can handle function* calling other function* without intermediate wrappers. The companion function is a bit tricky because it has to deal with a stack of generators.
The execution flow can be difficult to understand because of the little yield/next dance between your function* and the companion function. One way to understand the flow is to write an example with the library of your choice, add console.log statements both in your code and in the library, and run it.
If [the blocking io function] is executed by the function that calls
the generator, is it executed asynchronously? I'm guessing so because
to do otherwise would result in blocking behaviour.
I don't think generators do asynchronous task handling. With generators, only one thing is executing at the same time--it's just that one function can stop executing and pass control to another function. For instance,
function iofunc1() {
console.log('iofunc1');
}
function iofunc2() {
console.log('iofunc2');
}
function* do_stuff() {
yield iofunc1;
yield iofunc2;
console.log('goodbye');
}
var gen = do_stuff();
(gen.next().value)();
(gen.next().value)(); //This line won't begin execution until the function call on the previous line returns
gen.next(); //continue executing do_stuff
If you read some of the articles about nodejs generators:
http://jlongster.com/2012/10/05/javascript-yield.html
http://jlongster.com/A-Study-on-Solving-Callbacks-with-JavaScript-Generators
http://jlongster.com/A-Closer-Look-at-Generators-Without-Promises
...they all employ additional functions/libraries to add in asynchronous execution.
1: to write "linear" asynch code with generators, is it necessary for
there to be a calling function that iterates over the generator,
executing yielded functions as callbacks and returning the result of
the callback back into the generator?
Yes. Let's call it "launcher".
2: if the answer to question 1 is yes, the exactly how are the yielded
functions executed - asynchronously?
Inside the generator, you yield an array with: the function and its parameters. In the controlling caller (launcher), you use fn.apply(..,callback) to call the async, putting the call to "generator.next(data);" (resume) inside the callback.
the async function is executed asynchronously, but the generator will be "paused" at the yield point, until the callback is called (and then "generator.next(data)" is executed)
Full working lib and samples:
https://github.com/luciotato/waitfor-es6

Resources