Asynchronous? JavaScript is always synchronous when you executing a JavaScript block of code. In this case, no other JavaScript code will be executed concurrently.
Understand asynchronous
JavaScript is asynchronous if the function is doing some networking, for example, AJAX calls.
A quick example could be helpful:
console.log("a"); // networking or AJAX function
db.get("select * from table", function (result) {
console.log("b");
});
console.log("c");
will show the following in debugger console:
a c b
In the above code, a
happens first, then the db.get function starts, it return right away and the rest of the code after the function (ie, c
) will execute. Meanwhile, the networking / AJAX operation is going in the background. When the networking / AJAX request has completed and the response has been collected, then b
happens.
These two posts are helpful as well:
- How does Asynchronous Javascript Execution happen? and when not to use return statement?
- When is JavaScript synchronous?
Callback vs Promise
Callbacks
Given that JavaScript is asynchronous when doing networking, and we really need to execute some code after the networking completed (success or error), it’s time to use callback functions. In the first example, ‘b’ is actually an anonumous callback and we already know ‘b’ won’t happen until the response has been. Here is another example:
// define function with callback argument function
another_example(arg1, callback) {
// retrieve some data from table
var result = db.get('select \* from table where attr=arg1');
// callback is triggered and pass the result
callback(result);
}
// call another_exmaple
another_example(1, function(data) {
console.log(data);
});
The problem with callbacks is that when you have lots of callbacks, you will probably end up with a callback hell which looks like:
function1(function(x) {
function2(function(y) {
function3(function(z) {
function4(function(i) {
...
});
});
});
});
Promises
A promise is a a proxy for a value not necessarily known at its creation time. With promises, the calling code can then wait until that promise is fulfilled before executing the next step. To do so, the promise has a method then
, which accepts a function that will be invoked when the promise has been fulfilled. Promises require an additional abstraction layer, which means you will rely on a library.
A promise can be:
- fulfilled - The action relating to the promise succeeded
- rejected - The action relating to the promise failed
- pending - Hasn’t fulfilled or rejected yet
- settled - Has fulfilled or rejected
Our previous example could be written as:
function another_example(arg1) {
db.get("select * from table where attr=arg1").then(function (data) {
console.log(data);
});
}
another_example(1);
Real life example
The following is Node.js scenario when I was trying to move a file from one folder to another within the same bucket of Amazon S3.
Scenario
In test bucket, I had a list of files stored in a folder called unsold, I need copy them all from unsold to a folder called sold, and delete them afterwards. (Amazon S3 node.js SDK doesn’t provide any method can move file directly >_<)
Async
Async is a module which provides straight-forward, powerful functions to work with asynchronous JavaScript. Note: Async is not using promise pattern to handle callbacks, it uses some common patterns like parallel, series, waterfall instead.
exports.moveFile = function (files) {
// for each file in files
async.each(
files,
function iterator(file, callback) {
// initialize params
var copyParams = {
Bucket: "test",
CopySource: "test/unsold/" + file.filename,
Key: "sold/" + file.filename,
};
var deleteParam = {
Bucket: "test",
Key: "unsold/" + file.filename,
};
// copy file
s3.copyObject(copyParams, function (err, data) {
if (err) callback(err);
else {
// delete file if no error with copying
s3.deleteObject(deleteParam, function (err, data) {
if (err) callback(err);
else {
console.log("delete", data);
callback();
}
});
}
});
},
function allDone(err) {
//This gets called when all callbacks are called
if (err) console.log(err, err.stack);
}
);
};
Q
Q is a tool for creating and composing asynchronous promises in JavaScript.
The following snippet is a re-write of previous exmple using Q:
exports.moveFile = function (files) {
files.forEach(function (file) {
var copyParams = {
Bucket: "test",
CopySource: "test/unsold/" + file.filename,
Key: "sold/" + file.filename,
};
var deleteParam = { Bucket: "test", Key: "unsold/" + file.filename };
var copyObject = function (copyParams) {
var deferred = Q.defer();
s3.copyObject(copyParams, deferred.resolve, function (err, data) {
if (err) console.error(err);
else return defered.promise;
});
return deferred.promise;
};
copyObject(copyParams)
.then(function (res) {
s3.deleteObject(deleteParam, function (err, data) {
if (err) console.error(err);
else {
console.log("delete", data);
}
});
})
.then(function (err) {
if (err) console.log(err, err.stack);
});
});
};