I am new to JavaScript and I am so confused with callbacks vs normal function calls and when to use callbacks in a real scenario.
Can someone please tell me, how both the below implementations are different from each other? or a real case scenario that makes a callback useful than a normal function call?
Using the normal function call
function getDetails(){
setTimeout(() => {
console.log("DETAILS")
}, 2000);
}
function getUser(){
setTimeout(() => {
console.log("USER");
getDetails(); // Normally calling the function
}, 3000);
}
getUser();
Using Callback
function getDetails(){
setTimeout(() => {
console.log("DETAILS")
}, 2000);
}
function getUser(callback){
setTimeout(() => {
console.log("USER");
callback(); // Calling the function
}, 3000);
}
getUser(getDetails);
There is no difference technically in the two examples you showed (assuming you won't modify getDetails before it is called). What makes it useful is that the function that calls the callback doesn't have to know the exact function to call (and could be used with different ones as needed). For instance, something like an event listener or the callback to Array.prototype.map only makes sense with the callback pattern.
But the scenario you showed ideally wouldn't use either - it should be restructured to use async/await:
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
async function getDetails (user) {
await sleep(2000)
console.log('DETAILS', user)
return 'some details'
}
async function getUser (userId) {
await sleep(3000)
console.log('USER', userId)
return 'some user'
}
async function main () {
const user = await getUser(123)
const details = await getDetails(user)
console.log('got these details:', details)
}
main().catch(e => console.error('Failed to fetch data:', e))
// If you are in an environment that supports top-level await,
// you can just use `await main()` instead
I added some more example stuff to illustrate a real use case.
Related
I have a question about async.each behavior.
consider the code:
const examples = [1,2];
const asyncTask = (index) => {
return new Promise(resolve => {
setTimeout(() => {
console.log(`Inside setTimeout-${index}`);
resolve(true);
}, 1500)
});
}
function testingAsyncEach(callback){
async.each(examples, (example, innerCallback) => {
asyncTask(example).then(isDone => {
console.log(`isDone: ${isDone}`);
innerCallback(null);
});
}, err => {
console.log('done testingAsyncEach');
return callback()
})
}
testingAsyncEach(() => {console.log('final callback testingAsyncEach');})
a simple code using the "async" module in nodejs, using the array [1,2] and on each item in the array executing the function "asyncTask" which returns a new promise which gets resolve after a timeout of 1.5 seconds.
in this scenario the output of the program is:
Inside setTimeout-1
isDone: true
Inside setTimeout-2
isDone: true
done testingAsyncEach
final callback testingAsyncEach
but when I changed the "testingAsyncEach" function to use the "await" syntax:
function testingAsyncEach(callback){
async.each(examples, async (example, innerCallback) => {
const isDone = await asyncTask(example);
console.log(`isDone: ${isDone}`);
innerCallback(null);
}, err => {
console.log('done testingAsyncEach');
return callback()
})
}
the async.each is not waiting for the "asyncTask" to end. output:
Inside setTimeout-1
isDone: true
done testingAsyncEach
final callback testingAsyncEach
Inside setTimeout-2
isDone: true
Can you please help me understand why using the "await" instead of the "then" change the behavior? how can I still use the "await" syntax and get the proper output?
Thanks!
According to the documentation of the async module, if you pass an async function, it won't pass the innerCallback to your function. I believe that your innerCallback(null) call therefore errored, making the async.each return early. (the 2nd example is already "launched" though, so that still happens afterwards)
Check if err is set to an error, and if so, log it.
Should that be the issue, removing the innerCallback argument and call should solve it.
I have the following code:
function download(url, callback) {
setTimeout(() => {
// script to download the picture here
console.log(`Downloading ${url} ...`);
callback();
}, 3* 1000);
}
download(url);
Why do I need to have a callback function. Can't I just create another function and call that function from within the download function? I don't see the point people saying that callbacks are needed for async programming.
Callbacks are necessary when a value depends on the response of a promise. Often when we request data from other sources, such as an external API, we don’t always know when our data will be served back.
I think what your example is alluding to would be something like this:
function download(url, callback) {
console.log(`Downloading ${url} ...`);
fetch(url)
.then((response) => {
callback(null, response)
})
.catch((error) => {
callback(err, null)
});
}
download("http://example.com/movies.json", function(err, response){
// Do something with the response data
});
Can't I just create another function and call that function from within the download function?
It would make more sense to pass your other function as the callback, like so:
function handleMovieData(err, response) {
// Do something with the response data
}
download("http://example.com/movies.json", handleMovieData);
Nick Parsons' comment explains this well
EDIT: Alternatively to passing in a callback function, you could utilize async/await (untested)
async function download(url) {
console.log(`Downloading ${url} ...`);
return new Promise(function(resolve, reject) {
fetch(url)
.then((response) => {
resolve(response)
})
.catch((error) => {
reject(err)
});
})
}
const movieData = await download("http://example.com/movies.json");
handleMovieData(movieData);
I have a series of API calls I need to make from a 'user profile' page on my app. I need to prioritize or order the calls when I land on the component.
I have tried using async-await on the componentDidMount lifecycle method but when the first call fails, the rest do not get called.
...
async componentDidMount() {
await user.getGameStats();
await user.getFriendsList();
await user.getPlayStyle();
}
...
Despite ordering the calls, I would like them to still execute regardless of whether the preceding calls failed.
You need to account for rejected promises. If you don't catch the error it will stop execution. Just add a catch() block to each function that may fail.
function a(){
return new Promise((r,f)=>{
console.log('a');
r();
});
}
function b(){
return new Promise((r,f)=>{
console.log('b');
f(); // rejecting this promise
});
}
function c(){
return new Promise((r,f)=>{
console.log('c');
r();
});
}
async function d(){
throw new Error('Something broke');
}
(async ()=>{
await a().catch(()=>console.log('caught a'));
await b().catch(()=>console.log('caught b'));
await c().catch(()=>console.log('caught c'));
await d().catch(()=>console.log('caught d'));
})();
It's a dirty solution, but you can do something like this:
user.getGameStats().then({res => {
callGetFriendsList();
}).catch({err =>
callGetFriendsList();
});
function callGetFriendsList() {
user.getFriendsList().then(res => {
user.getPlayStyle();
}).catch(err => {
user.getPlayStyle();
});
}
The ideal and good way would be to call all of them at the same time asynchronously if they are not dependent on the response of the previous request.
Just add an empty catch at the end of each API call as following
async componentDidMount() {
await user.getGameStats().catch(err=>{});
await user.getFriendsList().catch(err=>{});
await user.getPlayStyle().catch(err=>{});
}
I'd catch to null:
const nullOnErr = promise => promise.catch(() => null);
async componentDidMount() {
const [gameStats, friendsList, playStyle] = await Promise.all([
nullOnErr(user.getGameStats()),
nullOnErr(user.getFriendsList()),
nullOnErr(user.getPlayStyle())
]);
//...
}
I also used Promise.all to run the calls in parallel as there seems to be no dependency between the calls.
Let's say I have a simple Node.js app that has these methods in another file:
module.exports = {
completeQuest(data) {
// Code here
},
killMonster(data) {
// Also code here
},
};
And they are not AJAX commands. These just manipulate some data within the app. I'd export as such in allActions.js:
const player = require('./playerActions');
module.exports = {
player,
};
and of course later, in main JS file const actions = require('./allActions');
So, generally, I'd do:
actions.player.killMonster();
actions.player.completeQuest();
But I want them to act one after the other. I know I can do async/await, but I'm not doing any AJAX calls, so would a Promise still be the best way?
What about using a yield function? Would that be good? I'm looking for opinions is all. Thank you.
I'm going to assume that killMonster and completeQuest perform asynchronous actions. (You've said they're "not ajax", but your question suggests they're not synchronous, either.)
Yes, this is a use case for promises, either explicit promises or those provided by async functions. Have killMonster and completeQuest return a promise (either by doing so explicitly or making them async functions), and then either:
actions.player.killMonster()
.then(
() => actions.player.completeQuest()
)
.catch(error => {
// Handle error
});
or, within an async function:
try {
await actions.player.killMonster();
await actions.player.completeQuest();
} catch (error) {
// Handle error
}
Here's a simple async/await example using setTimeout to provide the asynchronous part:
const delay = (ms, ...args) =>
new Promise(resolve => {
setTimeout(resolve, ms, ...args);
});
// Stand-ins for the actions
const player = {
actions: {
async killMonster() {
await delay(500, "monster killed");
},
async completeQuest() {
await delay(800, "quest complete");
}
}
};
// Top-leve async function (see my answer here:
// https://stackoverflow.com/questions/46515764/how-can-i-use-async-await-at-the-top-level
(async () => {
try {
console.log("Killing monster...");
await player.actions.killMonster();
console.log("Monster killed; completing question...");
await player.actions.killMonster();
console.log("Quest complete");
} catch (e) {
// Deal with error (probably don't just dump it to the console like this does)
console.error(e);
}
})();
Let's say I have two async events, both need to i/o with remote exchange.
placeOrder()
cancelOrder()
Both events fire in async way, which means cancelOrder can be called before placeOrder return. Tricky part is I need the placeOrder to return an Order ID first otherwise there is no way to call cancelOrder, so I need some way to block the cancelOrder event right until placeOrder returns, and the blockage cannot be too long otherwise the Order may be executed, so loop/timeout/frequent checking doesn't work here.
Any idea?
You would use a Promise for that. If your functions already return a promise, you can simply chain the both functions using then()
placeOrder().then(val => cancelOrder(val));
If they do not, you can put them inside a new Promise
function foo() {
return new Promise((resolve, reject) => {
// do stuff
resolve('<result of placeOrder here>');
});
}
function bar(val) {
return new Promise((resolve, reject) => {
// do stuff
resolve('whatever')
})
}
and call
foo()
.then(value => bar(value))
.then(console.log);
If you are able to use ES2017, the you can use async functions. For example, I'm going to assume that your functions perform some sort of request to the database using fetch or axios since you haven't specified. Then you can write placeOrder and cancelOrder like so:
const placeOrder = async () => {
try {
const response = await fetch('/place_order');
// Do something with the response
} catch (err) {
// Handle error
}
};
const cancelOrder = async () => {
try {
const response = await fetch('/cancel_order');
// Do something with the response
} catch (err) {
// Handle error
}
};
const someFunction = async () => {
await placeOrder();
await cancelOrder();
};