back

by danabramov·13y ago·view on hn ↗
How 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...

10 comments
Coroutines (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(){/*...*/});
Just hope that the players won't enter too much invalid names : http://stackoverflow.com/a/7828803/260556
Not quite in this case. I'm making an assumption that the Ask function provided by the parent is actually asynchronous.

  (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.
I 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.
I'd do that with the aforementioned `async` library, specifically `async.eachSeries`:

    async.eachSeries players, ask, ->
       # Well that was easy enough...
       printRoster(players)
Where is the part that checks if the name was invalid and then asks again?

    function 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);
      });
    }
You 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.

  for 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
This 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.

Hmm, I wrote it so that the function closes over the player, so that shouldn't happen. The real issue is, as pointed out, that this will ask for all the names at once, rather than sequentially. Wether this is bad or not depends on how the `ask` function gets its input.
This will ask two players simultaneously. My example waits for each player to provide a valid name in turn.
ok, 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
Exactly, 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.

Point 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)
	}
Here 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.
Maybe 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;
      }
    });
  });
Same 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.
If 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.
Because 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.
This. Specifically, I'm thinking about iOS prompts and alerts, they are not blocking.
Great comment! I missed that.
Well, if the Ask function should only run once at a time, it should block itself.
Imagine it's an iOS modal prompt. It doesn't block the thread, it sends an event.