Deep dive into Node.js Async and Sync
Sandip Das
Senior Cloud & DevOps Engineer | Full Stack Developer | Kubernetes Expert | AWS Container Hero | Teacher, Mentor, Career Coach | "learnTechWithSandip" on YouTube | Cloud , DevOps & Programming Content Creator
This post is I am writing for junior and intermediate node.js developers , hope they will get good understanding on subject after reading the entire post .
For last 3 years I am working in a lot of Node.js application development with express.js + MongoDb and with different frameworks e.g. Sails.js, Mean.io,Meteor.js , Hapi.js, Kraken.js etc, while developing application I faced many challenges , and one of most important challenge is when I worked with multiple callback loops a.k.a Callback Hell , and there come the use of Node.js Async and Sync function and modules, here I am going to discuss in deep about them.
What Is Node.js again ?
By Wikipedia Defination:
In software development, Node.js is an open-source, cross-platform runtime environment for developing server-side Web applications. Although Node.js is not a JavaScript framework, many of its basic modules are written in JavaScript, and developers can write new modules in JavaScript.The runtime environment interprets JavaScript using Google's V8 JavaScript engine.
In simple term:
Node.js is built on top of Google's V8 engine, which in turns compiles JavaScript.
Synchronous Programming
In traditional programming practice, most I/O operations happen synchronously.
What happens in the background?
The main thread will be blocked until the file is read, which means that nothing else can be done in the meantime. To solve this problem and utilize your CPU better, you would have to manage threads manually. If you have more blocking operations, the event queue gets even worse.
To resolve this issue, Node.js introduced an asynchronous programming model.
Node.js Asynchronous Nature:
Asynchronous I/O is a form of input/output processing that permits other processing to continue before the transmission has finished.
As many of you already know, JavaScript is asynchronous in nature. Asynchronous is a programming pattern which provides the feature of non-blocking code i.e do not stop or do not depend on another function / process to execute a particular line of code.
Asynchronous is great in terms of performance, resource utilization and system throughput and this is a powerful concept that enables gains in efficiency, but occasionally requires a bit more work on your end to deal with certain situations:
One of those situations, which I’ve run into quite frequently, is the need to wait for a number of asynchronous operations to finish before executing additional code.
Very difficult for a legacy programmer to proceed with Async.
Handling control flow is really painful.
If you are a function-oriented programmer, then it would be little difficult for you to grasp asynchronous programming. However, if you are familiar with multithreading in Java, then this is similar to that.
Compare Synchronous and Asynchronous
Node.js uses ECMAScript as its core language, hence it adopts every aspect of ECMAScript-like generators, fibers, asynchronous nature of code, etc. Consider following code, which is blocked in nature.
consider following Synchronous code:
var fs = require("fs");
fs.readFileSync(xyz.txt’,function(err,data){
if(!err) {
console.log(data);
}
});
console.log("something else");
Now consider following Asynchronous code:
var fs = require("fs");
fs.readFile(xyz.txt’,function(err,data){
if(!err) {
console.log(data);
}
});
console.log("something else");
Difference Between above two?
In the first module, the program was waiting while reading the file. It will not go further before completing the read operation, which is an example of blocking code. But ideally, we should proceed further while the program was reading the file and once it is done we should go back and process that. That is what happening in the second code.
In the second code, the program is not waiting, hence you see the console first and file contents later. However, controlling the flow of this kind of program will cost you a little more time and work. Before moving further, ask yourself: how you are going to know whether or not the file reading has been done?
There are plenty of ways to do so, but one of the popular ways is using Callback, which is like a notification system in Asynchronous programming. As the name implies, it will notify you when the Async task is completed. But wait, we have not solved the puzzle yet!
Having callbacks in your code does not mean you have control over the asynchronous nature of JavaScript. Most programmers make this mistake and get caught up in callback hell.
Callback Hell :
Having a series of callbacks after callbacks is known as the "callback hell" in the JavaScript zone. In this example, one callback stops the execution of code, waits for data, and then passes it to the next function, which again has callbacks and puts event loops to a suspended state until processing finishes, and so on.
It looks something like this:
Why Isn't it Good to Have Multiple Callbacks?
When you use callbacks, you ultimately stop the execution flow of your whole program. Having fewer callbacks is not an issue, but if you have too many callbacks, your program will be in more of a waiting state than an executing state. You really don’t wanna do that, 'cause as said by Google, "Don’t present it unless it's fast".
What’s the solution?
To avoid callback hell, you need to understand the design the program flow in such a way that you program utilizes resources and works fast. Node.js has plenty of modules to control the flow of a program, and one of the most popular ones is Async.js and other packages like sync or synchronize.
Async.js
To avoid the so-called Callback-Hell one thing you can do is to start using async.js.
Async is a utility module which provides straight-forward, powerful functions for working with asynchronous JavaScript. Functions are categorized in a collection, and control the flow along with utils.
Async.js helps to structure your applications and makes control flow easier.
Quick Examples:
async.map(['file1','file2','file3'], fs.stat, function(err, results){
// results is now an array of stats for each file
});
async.filter(['file1','file2','file3'], fs.exists, function(results){
// results now equals an array of the existing files
});
async.parallel([
function(){ ... },
function(){ ... }
], callback);
async.series([
function(){ ... },
function(){ ... }
]);
async.waterfall([
function (callback) {
getSomething(options, function (err, result) {
if (err) {
callback(new Error("failed getting something:" + err.message));
// we should return here
}
// since we did not return, this callback still will be called and
// `processData` will be called twice
callback(null, result);
});
},
processData
], done)
When I started using async , I am so confused about how to use it and when to use what function , and I believe more developer there like me who at first not able to determine in which circumstances to use which async function , now I am going to discuss about that .
Async parallel
Run the tasks collection of functions in parallel, without waiting until the previous function has completed. If any of the functions pass an error to its callback, the main callback is immediately called with the value of the error. Once the tasks have completed, the results are passed to the final callback as an array.
Note: parallel is about kicking-off I/O tasks in parallel, not about parallel execution of code. If your tasks do not use any timers or perform any I/O, they will actually be executed in series. Any synchronous setup sections for each task will happen one after the other. JavaScript remains single-threaded.
It is also possible to use an object instead of an array. Each property will be run as a function and the results will be passed to the final callback as an object instead of an array. This can be a more readable way of handling results from async.parallel.
Now let's check a real application of it:
let's say we have a below code:
app.get('/user/:userId', function(req, res, next) {
var locals = {};
var userId = req.params.userId;
var callbackCounter = 0;
var gotError = false;
db.get('users', userId, function(err, user) {
if (gotError) {
return;
}
if (err) {
gotError = true;
return next(err);
}
locals.user = {
name: user.name,
email: user.email,
bio: user.bio
};
callbackCounter++;
if (callbackCounter == 2) {
res.render('user-profile', locals);
}
});
db.query('posts', {userId: userId}, function(err, posts) {
if (gotError) {
return;
}
if (err) {
gotError = true;
return next(err);
}
locals.posts = posts;
callbackCounter++;
if (callbackCounter == 2) {
res.render('user-profile', locals);
}
});
});
In above example code their is two db calls are asynchronous we don’t know which one of them is going to finish first.So we have to use callbackCounter to keep track of how many tasks have finished. If an error occurs we also have to handle this in a special way for each task. And we have code duplication.
And what happens when we need to add another asynchronous task? Then we need to change if (callbackCounter == 2) to if (callbackCounter == 3), which won’t be fun to maintain in the long run.
This is where Async comes to our aid and makes the code sane to look at and easy to maintain. In this post I’ll give you some pointers on how how to use Async in real life.
Important note about callbacks and errors
One thing that wasn’t obvious to me when I first looked at Async, was the way callbacks are used.
Generally all the Async functions take a set of tasks to perform as argument. These tasks can for example be an array of functions, or a collection to iterate over. Each task is given a callback function, let’s call this the task callback. This callback must be called when the task is completed, e.g. after an asynchronous call to the database has completed.
Besides the set of tasks the Async functions also take a callback function as argument themselves. Let’s call this the final callback. The final callback is called when all tasks have completed, i.e. called their respective task callback functions.
Example:
async.parallel([
function(callback) { //This is the first task, and `callback` is its callback task
db.save('xxx', 'a', function(err) {
//Now we have saved to the DB, so let's tell Async that this task is done
callback();
});
},
function(callback) { //This is the second task, and `callback` is its callback task
db.save('xxx', 'b', callback); //Since we don't do anything interesting in db.save()'s callback, we might as well just pass in the task callback
}
], function(err) { //This is the final callback
console.log('Both a and b are saved now');
});
If a task encounters an error, the best thing is to call the task callback with the error object as the first argument.
When a task calls back with an error, the final callback will be called immediately with the error object, and no more outstanding tasks will be initiated.
Example:
async.parallel([
function(callback) {
db.save('xxx', 'a', function(err) {
if (err) {
callback(err);
//It's important to return so that `callback` isn't called twice
return;
}
callback();
});
},
function(callback) {
//If we just pass in the task callback, it will automatically be called with an error, if the db.save() call fails
db.save('xxx', 'b', callback);
}
], function(err) {
if (err) {
//Handle the error in some way. Here we simply throw it
//Other options: pass it on to an outer callback, log it etc.
throw err;
}
console.log('Both a and b are saved now');
});
These 4 lines of error handling gets pretty tedious:
if (err) {
callback(err);
return;
}
So I prefer to put those lines on one line, as in:
if (err) return callback(err);
another example of async.parallel
I need to run multiple tasks that doesn’t depend on each other and when they all finish do something else
Then you should use async.parallel. The signature is async.parallel(tasks, callback), where tasks is an array of functions. It will immediately run all the functions in parallel, wait for all of them to call their task callback, and finally when all tasks are complete it will run callback (the final callback).
An example could be to load a forum user’s profile with their details and a list of all their posts.
As input we get the user’s ID, so we can easily get both user details and posts independently of each other (in parallel).
app.get('/user/:userId', function(req, res, next) {
var locals = {};
var userId = req.params.userId;
async.parallel([
//Load user
function(callback) {
db.get('users', userId, function(err, user) {
if (err) return callback(err);
locals.user = {
name: user.name,
email: user.email,
bio: user.bio
};
callback();
});
},
//Load posts
function(callback) {
db.query('posts', {userId: userId}, function(err, posts) {
if (err) return callback(err);
locals.posts = posts;
callback();
});
}
], function(err) { //This function gets called after the two tasks have called their "task callbacks"
if (err) return next(err); //If an error occurred, we let express handle it by calling the `next` function
//Here `locals` will be an object with `user` and `posts` keys
//Example: `locals = {user: ..., posts: [...]}`
res.render('user-profile', locals);
});
});
If you have more than two tasks to run, you just add to the tasks array (the first argument to async.parallel).
Note that in a real life application you would probably want to modularize your functions a bit more. Instead of using two anonymous functions to get a user and load a user’s posts respectively, you could import two modules called getUser and getUserPosts. That would tidy up the code a lot. I’ll expand on this in a future post. This post focuses on explaining the workings of async.js with as few distractions as possible.
Async series
When to use it?
You need to run multiple tasks that depends on each other and when they all finish do something else
Then you should use async.series. The signature is async.series(tasks, callback), where tasks is an array of functions. It will run one function at a time, wait for it to call its task callback, and finally when all tasks are complete it will run callback (the final callback).
The Async series() allows us to control the execution flow of callbacks. The series() method takes an array of functions as an argument (or an object that contains the functions). The functions are executed in the order in which they exist in the array. Each callback function in the list takes the err parameter as the first argument to the function, and the second argument is the return value (if there is one).
The series() function also accepts a second argument, a callback function that contains the results of the functions passed in as the first parameter.
To demonstrate how the series() method can clean up code, the following example has three timer tasks. Each task executes as a callback within the other, all the way to the top or to the first timer task. The code looks like this:
var results = [];
setTimeout(function() {
console.log(“Task 1”);
results[0] = 1;
setTimeout(function() {
console.log(“Task 2”);
results[1] = 2;
setTimeout(function() {
console.log(“Task 3”);
results[2] = 3;
}, 100);
}, 200);
}, 300);
It looks a bit complicated and messy. The example below shows the same code
rewritten using the async.series() function:
var async = require(“async”);
async.series([
function(callback) {
setTimeout(function() {
console.log(“Task 1”);
callback(null, 1);
}, 300);
},
function(callback) {
setTimeout(function() {
console.log(“Task 2”);
callback(null, 2);
}, 200);
},
function(callback) {
setTimeout(function() {
console.log(“Task 3“);
callback(null, 3);
}, 100);
}
], function(error, results) {
console.log(results);
});
Another real life example:
This time we get the user’s name as input, but our data model is the same as before. This means that we need to find the user’s id based on their name before we can load the posts. This means we can’t run it in parallel as we did before.
app.get('/user/:name', function(req, res, next) {
var locals = {};
var name = req.params.name;
var userId;
//Define `userId` out here, so both tasks can access the variable
async.series([
//Load user to get `userId` first
function(callback) {
db.query('users', {name: name}, function(err, users) {
if (err) return callback(err);
//Check that a user was found
if (users.length == 0) {
return callback(new Error('No user with name '+name+' found.'));
}
var user = users[0];
userId = user.id;
//Set the `userId` here, so the next task can access it
locals.user = {
name: user.name,
email: user.email,
bio: user.bio
};
callback();
});
},
//Load posts (won't be called before task 1's
// "task callback" has been called)
function(callback) {
db.query('posts', {userId: userId}, function(err, posts) {
if (err) return callback(err);
locals.posts = posts;
callback();
});
}
], function(err) {
//This function gets called after the two tasks have called
//their "task callbacks"
if (err) return next(err);
//Here locals will be populated with `user` and `posts`
//Just like in the previous example
res.render('user-profile', locals);
});
});
In this example you don’t gain that much from using async.series, since you only have two tasks to run. The above example could (depending on your taste) be simplified to the following using nesting:
app.get('/user/:name', function(req, res, next) {
var name = req.params.name;
//Get user by name
db.query('users', {name: name}, function(err, users) {
if (err) return next(err);
if (users.length == 0) {
return callback(new Error('No user with name '+name+' found.'));
}
var user = users[0];
//Load user's posts
db.query('posts', {userId: user.id}, function(err, posts) {
if (err) return next(err);
locals.posts = posts;
//We're done and can render the template to the client
res.render('user-profile', {
user: {
name: user.name,
email: user.email,
bio: user.bio
},
posts: posts
});
});
});
});
But what happens when you suddenly need to run 3 different tasks? Or even more? Then you end up cooking callback spaghetti (bad).
IMO when you have two levels you can use either solution. If you have more, always go with async.series.
Async forEach
When to use it ?
you need to iterate over a collection, perform an asynchronous task for each item, and when they’re all done do something else.
The signature is async.forEach(items, task, callback).items is the collection you want to iterate over and task is the function to call for each item in items. Async will immediately call task with each item in items as the first argument. All tasks are run in parallel. Example: task(item[0]), task(item[1]) …task(item[n]). Once all tasks complete the final callback will be called.
An example could be a webservice where you support deleting multiple messages in one request. You get the message IDs as a comma separated string in the URL. Each deletion requires a separate call to the database. When all deletions have completed you want to reply the user with a response.
Async forEachLimit
When to use it ?
You need to iterate over a collection, perform an asynchronous task for each item, but only let x tasks run at the same time, and when they’re all done do something else. But what if your database only allows a limited number of connections at a time, and your user might delete thousands of messages in a single request? Then you useasync.forEach’s sibling async.forEachLimit.
The signature is async.forEachLimit(items, concurrency, task, callback). It works almost like async.forEach except that it doesn’t run task for all items immediately in parallel. The concurrency value is an integer that tells Async how many tasks are allowed to run simultaneously. Let’s say that our database only allows 5 connections at a time, then we simply change our code to:
app.delete('/messages/:messageIds', function(req, res, next) {
var messageIds = req.params.messageIds.split(',');
//`5` is the `concurrency` argument here
// ----------------------------?
async.forEachLimit(messageIds, 5, function(messageId, callback) {
db.delete('messages', messageId, callback);
}, function(err) {
if (err) return next(err);
res.json({
success: true,
message: messageIds.length+' message(s) was deleted.'
});
});
});
If you are working with large collections it’s normally a good idea to use async.forEachLimit in favor of async.forEach to throttle i/o resources.
Async forEachSeries
When to use it ?
I need to iterate over a collection, perform an asynchronous task for one item at a time, and when they’re all done do something else
The third async.forEach sibling is async.forEachSeries, which does the same asasync.forEachLimit with a concurrency of 1. The signature is async.forEachSeries(items, task, callback), and it simply handles each item in items serially, or one at a time.You can use this if it’s important that the task of one item finishes before the task of the next one is started. One example could be if each task depends on some result from the previous task. Or if each task share some state or external service that does not handle multiple clients.
Async queue
When to use it?
You need to perform an arbitrary set of asynchronous tasks .
The syntax of async.queue is a little different than the other functions. The signature isasync.queue(task, concurrency).
The task function itself should take two arguments. The first is the task to be performed. This can be anything that the function can use to perform its task. Second argument is a callback, which will be the task callback if we use the same terminology as earlier, that should be called when the task is done.
The concurrency value is just like the one from async.forEachLimit, i.e. it limits how many tasks can be executed at a time.
async.queue returns an object where you can push tasks to, using queue.push(task). Read about the other properties of the object on the Github page. The most useful property isdrain. If you set this to a function it will be called every time the queue’s last task has been processed, which is very useful for performing an action when queue processing is done. Think of it as async.queue’s final callback.
A good example of using a queue is when your input is streamed from another source, which makes it difficult to use async.forEach. An example could be to copy all objects from one AWS S3 bucket to another. Since AWS only lets you list 1000 objects at a time, you can’t get a single array with all object names from the source bucket at once. You have to list 1000 objects at a time, and supply the last object name from the previous response as the marker in the next request (just like pagination). You could choose to load all object names into a single array first, but then you’d have to list all objects, and not until they’ve all been listed you can start copying - but that would be a terrible waste of valuable time. Nor would it scale if you had billions of S3 files.
A smarter way is to set up an async.queue, and add object names to the queue as we get them from the list. As I said, a queue task can be anything. In this case an S3 object name is a task.
Let’s get some code on the table. In this example I’m using the API of Apps Attic’sawssum module for AWS services (awesome name by the way).
//Prepare S3 access and bucket names
var awssum = require('awssum');
var s3 = new awssum.load('amazon/s3').S3({
accessKeyId: '...',
secretAccessKey: '..',
});
var sourceBucket = 'old-bucket';
var destinationBucket = 'new-bucket';
var listObjectsDone = false;
//Set up our queue
var queue = async.queue(function(objectName, callback) {
//This is the queue's `task` function
//It copies `objectName` from `sourceBucket` to `destinationBucket`
var options = {
BucketName: destinationBucket,
ObjectName: objectName,
SourceBucket: sourceBucket,
SourceObject: objectName
};
s3.CopyObject(options, function(err) {
if (err) throw err;
callback(); //Tell async that this queue item has been processed
});
}, 20); //Only allow 20 copy requests at a time so we don't flood the network
//When the queue is emptied we want to check if we're done
queue.drain = function() {
checkDone();
};
//Define the function that lists objects from the source bucket
//It gets the current `marker` as its argument
function listObjects(marker) {
var options = {
BucketName: sourceBucket,
Marker: marker,
MaxKeys: 1000
};
s3.ListObjects(options, function(err, data) {
if (err) throw err;
var result = data.Body.ListBucketResult;
var contents = _.isArray(result.Contents) ? result.Contents : [result.Contents]; //AWS sends an array if multiple, and a single object if there was only one result
_.each(contents, function(item) {
var objectName = item.Key;
marker = objectName; //Save the marker
queue.push(objectName); //Push the object to our queue
});
if (result.IsTruncated == 'true') {
//The result is truncated, i.e. we have to list once more, starting from the new marker
listObjects(marker);
} else {
//Tell our routine that we don't need to wait for more objects from S3
listObjectsDone = true;
//Check if we're done (is the queue empty?)
checkDone();
}
});
}
/*
This function gets called when:
a) `listObjects` didn't return a truncated result (because we were at the end of the bucket)
b) when the last task of the queue is finished
*/
function checkDone() {
if (queue.length() == 0 && listObjectsDone) {
console.log('Tada! All objects have been copied :)');
}
}
//Start the routine by calling `listObjects` with `null` as the `marker`
listObjects(null);
Note that the queue can be drained multiple times, and thereby call queue.drain()multiple times. This would for example happen if our copy requests finished much faster than each list operation. That’s why we have the listObjectsDone boolean. Even if the queue is empty we’re not done until this variable gets set to true.
One missing feature of async.queue that you should be aware of is that the task callbacks do not support being passed an error as its first argument. Check out this example:
I would expect this to print out 1 and 2, and then I would see the error somewhere. But it will print out 1, 2 and 3.
This is a good feature when you think about it though. What would we want to have happen in our S3 copy example above if a single copy operation failed? Would we want the queue to stop automatically and halt our job midways? No, not really. Depending on your use case you should handle the error accordingly. If an S3 copy operation fails fx, you could retry it a maximum of 5 times, and then give up and just don’t copy it, but let the job continue anyway.
Async Waterfall
When to use it ?
You need to pass values between the functions in a trickle-down manner .
The method is useful when you need the results of a previous function to perform an operation with the next function in the series. An example appears below:
var async = require(“async”);
async.waterfall([
function(callback) {
callback(null, 12, 15);
},
function(a, b, callback) {
callback(null, (a + b) * 10);
},
function(cc, callback) {
callback(null, Math.sqrt(cc));
}
], function(error, c) {
console.log(c);
});
In this example, the numbers 12 and 15 were added, which was handled by the second function. The result of this addition is passed into the next function as the parameter , which applied to the function. The final result is the square root of (12 + 15) * 10, which is passed out using the parameter to the method’s final callback function.
Notice that the method allows you to pass in extra parameters to callback functions, not just the and value as with the method.
Take a look at this piece of code with three levels deep async callbacks.
var fs = require('fs');
var http = require('http');
var path = './headers.txt';
// i. check if headers.txt exists
fs.stat(path, function(err, stats) {
if (stats == undefined) {
// ii. fetch the HTTP headers
var options = {
host: 'www.wikipedia.org',
port: 80
};
http.get(options, function(res) {
var headers = JSON.stringify(res.headers);
// iii. write the headers to headers.txt
fs.writeFile(path, headers, function(err) {
console.log('Great Success!');
});
});
}
else { console.log('headers already collected'); }
});
With every level it gets worse and worse. Imagine it was 10 levels deep! You must have figured the async callback problem by now. Let's see how we can fix it.
var fs = require('fs');
var async = require('async');
var http = require('http');
var path = './headers.txt';
async.waterfall(
[
// i. check if headers.txt exists
function(callback) {
fs.stat(path, function(err, stats) {
if (stats == undefined) { callback(null); }
else { console.log('headers already collected'); }
});
},
// ii. fetch the HTTP headers
function(callback) {
var options = {
host: 'www.wikipedia.org',
port: 80
};
http.get(options, function(res) {
var headers = JSON.stringify(res.headers);
callback(null, headers);
});
},
// iii. write the headers to headers.txt
function(headers, callback) {
fs.writeFile(path, headers, function(err) {
console.log('Great Success!');
callback(null, ':D');
});
}
],
// the bonus final callback function
function(err, status) {
console.log(status);
}
);
Isn't that much better? With the use of async, callbacks don't grow horizontally, they grow vertically, very much like synchronous calls.
While the async module offers many other useful methods, only two of them are enough for most async callbacks in Node.js - series() and waterfall().
Both these methods accept an array of functions to be executed one after another, and an optional callback function which will be executed after all the functions have executed without any errors.
The difference between series() and waterfall() is that the functions in series() are executed one after another and their results stored in a response array in their corresponding indexes, and the response array is passed to the optional final callback function. In waterfall(), the result of each function is passed on to the next function, and the result of the last function is passed to the optional final callback function.
In the above example, we used waterfall() because we needed to pass the result of step ii to the function in step iii.
Consider below simple example of using :
async.waterfall(
[
function(callback) {
callback(null, 'Node.js', 'JavaScript');
},
function(arg1, arg2, callback) {
var caption = arg1 +' and '+ arg2;
callback(null, caption);
},
function(caption, callback) {
caption += ' Rock!';
callback(null, caption);
}
],
function (err, caption) {
console.log(caption);
// Node.js and JavaScript Rock!
}
);
The first parameter, , in is for any error you might want to throw. If the error parameter is a non- value, other functions in the queue are not executed, the callback function is called with the error value immediately.
When the error value is , the following parameters after are passed on to the next callback function. Don't expect to find the error parameter in the next function even though it is the first parameter in . It never makes it to the next function. The last parameter of is understood as the next callback function.
Ok , I discussed a lot about Node.js async , but wait there is many more , Jump and check it out https://caolan.github.io/async/docs.html#
Sync or Synchronize
Sync or Synchronize are node.js modules build to run async functions in sync mode , and both built on node-fibers library as a multithreading solution, let's check out both one by one.
Sync
node-sync is a simple library that allows you to call any asynchronous function in synchronous way. The main benefit is that it uses javascript-native design - Function.prototype.sync function, instead of heavy APIs which you'll need to learn. Also, asynchronous function which was called synchronously through node-sync doesn't blocks the whole process - it blocks only current thread!
Example:
Simply call asynchronous function synchronously:
var Sync = require('sync');
function asyncFunction(a, b, callback) {
process.nextTick(function(){
callback(null, a + b);
})
}
// Run in a fiber
Sync(function(){
// Function.prototype.sync() interface is same as Function.prototype.call() - first argument is 'this' context
var result = asyncFunction.sync(null, 2, 3);
console.log(result); // 5
// Read file synchronously without blocking whole process? no problem
var source = require('fs').readFile.sync(null, __filename);
console.log(String(source)); // prints the source of this example itself
})
If you would like to know more abut Sync go and check it out https://github.com/ybogdanov/node-sync
Synchronize
It use and to execute asynchronous functions synchronously.
Synchronize assumes node-style callbacks. The second parameter given to the cb will be returned. If an error is present it will be thrown and can be intercepted by a try/catch statement. Multiple and named arguments are supported with .
You can use the helper and it will use await/defer automatically. The helper is non-destructive and the function can still be called asynchronously if called with a callback. You can also mix it with other synchronous or asynchronous code.
Use / manually to get precise control or automatically to write compact and clean code.
Example:
var sync = require('synchronize')
var fs = require('fs')
sync(fs, 'readFile')
sync.fiber(function(){
var data = fs.readFile(__filename, 'utf8')
console.log(data)
try {
data = fs.readFile('invalid', 'utf8')
} catch (err) {
console.log(err)
}
fs.readFile(__filename, 'utf8', function(err, data){
console.log(data)
})
})
If you would like to know more abut Synchronize go and check it out https://alexeypetrushin.github.io/synchronize/docs/index.html
All this I explained in this post is to make life little easier to new and intermediate node.js programmers , hope they will be benefited by this post , if you need more explanation on any point , just let me know on comment , I will give answers ASAP.
Thanks for reading!
Did you find any of these tips useful? please like and share with others .
Do you know more about this topic or just know better resources ? do give comment and share with others :)
Note: While writing this I tried to use best resources available in the web, as I see in many tutorials + I used in my past projects , if something seems outdated or any function depreciated in latest async or sync modules , please let me know I will update this post accordingly .
About the Author
Sandip Das is a tech start-up adviser as well as working for multiple international IT firms , tech-entrepreneurs as Individual IT consultant , Sr. Web Application developer , Cloud / Full Stack JavaScript Architect , worked as a team member, helped in development and in making IT decision . His desire is to help both the tech-entrepreneurs & team to help build awesome web based products , make team more knowledgeable , add new Ideas in product.
Senior Analyst | CFA Exam Level 1 Cleared | Data Science | Python | SQL | Tableau | Power BI | Machine Learning | Data Analysis
8 年Awesome posts for programmers lovers