5 ms·
I don't understand what the big deal is. Callbacks are OK. They're less cumbersome if the language you're using has smaller function definitions. Callbacks 'ge
by ryan-allen 13y ago
I don't understand what the big deal is. Callbacks are OK. They're less cumbersome if the language you're using has smaller function definitions.
Callbacks 'get crazy' when you've got more than one I think, and thankfully someone smart has made a library you can use to manage them!
https://github.com/caolan/async https://github.com/caolan/async
Saying that, I don't mind the way things look with the whole await/async stuff in C# and etc. However I don't think we should be waving our arms around saying callbacks are like goto, they so completely are not! I have written heaps of stuff with callbacks and it's _not that confusing or unmaintainable_. It's just different.
- danabramov 13y agoHow do you do this with callbacks? foreach (var player in players) { while (true) { var name = await Ask("What's your name"); if (IsValidName(name)) { player.name = name; break; } } } Assuming `Ask` is an asynchronous operation and must not block the UI thread. Note that second player is only asked after the first player has given a valid name. (And the code structure reflects that :-) My point is of course it's doable with callbacks, but I spent more time indenting this code than writing it, and I darn well know I'm not smart enough to spell out the correct callback-style code in a comment field on Hacker News. And if I suddenly had to add error handling...
- jonahx 13y agoI'll admit this took a lot longer to reason through than your example, but at the end of the day it's just a simple recursion (and I actually think the force validation bit is simpler in this version). Interesting exercise, and I think it ended up validating your point for me: function makeAskPlayer(i) { return function askPlayerHisName() { console.log("askPlayerHisName: " + i); Ask("What's your name?", function(name) { if (isValidName(name)) { players[i].name = name; } else { return (makeAskPlayer(i))(); } // name was valid, get next one if (i < players.length-1) (makeAskPlayer(i+1))(); }); }; } (makeAskPlayer(0))(); Here's a simple test case showing it works: http://jsbin.com/ojakuy/2/edit http://jsbin.com/ojakuy/2/edit. You need to open the console to see the output.
- jeswin 13y agoHere is an example where callbacks are even less intuitive: if not song.artist @getArtist song.id, (err, artist) => song.artist = artist @save song @addToCatalog song #... else @save song @addToCatalog song #... Callbacks will force you to move @save, @addCatalog, ... into a separate function. Completely messing up the logical sequence of operations.
- craigyk 13y agofor player in players get_name_for = (player) -> ask "what's your name?", (response) -> if is_valid_name response player.name = response else get_name_for player get_name_for player
- danabramov 13y agoThis will ask two players simultaneously. My example waits for each player to provide a valid name in turn.
- craigyk 13y agook, sure, but it's still fixable without having to use await. you'd have to forego the for loop and make that flow control part of the callback cycle. was your point that it couldn't be done with callbacks? or couldn't be done easily? or not easily alongside traditional flow control like for loops? I agree, await and async in C# are very nice, I just took your post as a challenge. get_player_name = (player, next) -> ask "what's your name?", (response) -> if is_valid_name response player.name = response next! else get_player_name player, next get_player_names = ([player,...players]) -> get_player_name player, -> if players.length > 0 get_player_names players get_player_names players
- danabramov 13y agoExactly, this was my point. It took me about as long as I typed this code to write it. Of course it is doable with callbacks, but I know I'm not smart enough to do it in a comment field on HN.
- anonymoushn 13y agoThis looks like it does the wrong thing. If "ask" gets to access the scheduler's task list as a queue, then entering an invalid name in the first response and only valid names thereafter will cause the first valid name to be given to the second player, the second valid name to the third player, and so on. If "ask" gets to access the scheduler's task list as a stack, then a sequence of only valid input names will cause the last player to have the first input name, the second-last player to have the second input name, and so on. Edit: I was optimistically assuming that the consumer of the many "asks" that are created all at once would process them sequentially, dealing with one and invoking the callback before dealing with the next. If you do not assume this, my problem disappears and you get the simpler problem of spawning many prompts simultaneously.
- singlow 13y agoMaybe like this? players.forEach(function() { 'use strict'; var player = this; var name = Ask("What's your name?, function(name) { if (isValidName(name) { player.name = name; } }); });
- danabramov 13y agoSame problem like with the sibling post: this will ask two players simultaneously. My example waits for each player to provide a valid name in turn.
- captaincrowbar 13y agoIf you're doing everything sequentially anyway, why bother with the awaiting part? As far as I can see your example would be functionally unchanged if you wrote the same code except without the await keyword.
- scott_s 13y agoBecause it doesn't block the thread. The idea is that this code is executed inside of a thread that, if it blocks, will cause the application to hang. For example, in a GUI or a server. So, if its a thread driving a GUI, and it's blocked on user input, then the entire application interface will be unresponsive until it receives that input.
- danabramov 13y agoThis. Specifically, I'm thinking about iOS prompts and alerts, they are not blocking.
- gruseom 13y agoGreat comment! I missed that.
- singlow 13y agoWell, if the Ask function should only run once at a time, it should block itself.
- anonymoushn 13y ago(function(players) { var cur_idx = 0; function cb(name, err) { // just kidding, not going to handle errors! if (IsValidName(name)) { players[cur_idx].name = name; cur_idx++; } if (cur_idx < players.length) { Ask("What's your name", cb); } } Ask("What's your name", cb); })(players); This is of course completely awful. just2n's reply is a nicer realization of the same concept. sprobertson's reply clearly will not work as written, and I don't expect it's even possible to condense this code into a single call to eachSeries.
- sprobertson 13y agoI'd do that with the aforementioned `async` library, specifically `async.eachSeries`: async.eachSeries players, ask, -> # Well that was easy enough... printRoster(players)
- esailija 13y agoWhere is the part that checks if the name was invalid and then asks again?
- just2n 13y agoCoroutines (or generators) are a really nice sugar for callbacks. This looks a lot like ES6's yield, just s/await/yield/. But to answer your question, since these can't be done in parallel, you'd have to keep track of which player you're asking: var playersAsked = 0; var askNextPlayerHisName = function(done){ if (playersAsked === players.length) done(); Ask("What's your name?", function(name){ if (IsValidName(name)){ players[playersAsked].name = name; playersAsked++; } askNextPlayerHisName(done); }); }; askNextPlayerHisName(function(){/*...*/});
- deleted 13y ago[deleted]
- deleted 13y ago[deleted]
- deleted 13y ago[deleted]
- asaarinen 13y agoYou do it like this (using async): async.eachSeries( players, function(player, playercb) { var valid = false; async.whilst( function() { return !valid; }, function(wcb) { Ask("What's your name", function(name) { if( IsValidName(name) ) { player.name = name; valid = true; } wcb(); }); }, playercb); }, function() { /* done */ } ); More lines than the foreach loop, but on the other hand, if this was an operation you wanted to do in parallel instead of sequentially, that'd be impossible with the simple loop construct.
- utopkara 13y agoPoint taken, yet this particular problem with players asked one after the other is simpler than the case when players are each spawned their own Ask. Here's a simple solution to your problem: function getAllNames(players, callback){ function getPlayer(i, players, callback){ Ask("What's your name", function(name){ if(isValidName(name)){ players[i++].name = name; } if(i == players.length){ callback(players); } else { getPlayer(i, players, callback); } }); } getPlayer(0, players, callback) }
- raycmorgan 13y agoI would do it in Node.js like the following: async.eachSeries(players, askName, function (err) {}); // this is someplace it should go function askName(player, callback) { Ask("What's your name", function (err, name) { if (err) { return callback(err); } if (IsValidName(name)) { player.name = name; callback(null); } else { askName(player, callback); } }); } This follows Node's err convention. Note I use a bit of old fashion recursion to handle the asking for a valid name. This will not stack overflow due to the fact Ask is async. You could inline askName, but then you wouldn't have a nice little unit testable function.
- stagas 13y agofunction askPlayers(players, fn) { if (!players.length) return fn(); var player = players[0]; ask("What's your name", function(name) { if (isValidName(name)) { player.name = name; players.shift(); } askPlayers(players, fn); }); }