Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript async function composition

I have several async functions with varying numbers of parameters, in each the last param is a callback. I wish to call these in order. For instance.

function getData(url, callback){
}
function parseData(data, callback){
}

By using this:

Function.prototype.then = function(f){ 
  var ff = this; 
  return function(){ ff.apply(null, [].slice.call(arguments).concat(f)) } 
}

it is possible to call these functions like this, and have the output print to console.log.

getData.then(parseData.then(console.log.bind(console)))('/mydata.json');

I've been trying to use this syntax instead, and cannot get the Then function correct. Any ideas?

getData.then(parseData).then(console.log.bind(console))('/mydata.json');
like image 793
John Williams Avatar asked Dec 30 '14 16:12

John Williams


Video Answer


2 Answers

Implementing a function or library that allows you to chain methods like above is a non-trivial task and requires substantial effort. The main problem with the example above is the constant context changing - it is very difficult to manage the state of the call chain without memory leaks (i.e. saving a reference to all chained functions into a module-level variable -> GC will never free the functions from memory).

If you are interested in this kind of programming strategy I highly encourage you to use an existing, established and well-tested library, like Promise or q. I personally recommend the former as it attempts to behave as close as possible to ECMAScript 6's Promise specification.

For educational purposes, I recommend you take a look at how the Promise library works internally - I am quite sure you will learn a lot by inspecting its source code and playing around with it.

like image 91
Robert Rossmann Avatar answered Oct 15 '22 05:10

Robert Rossmann


Robert Rossmann is right. But I'm willing to answer purely for academic purposes.

Let's simplify your code to:

Function.prototype.then = function (callback){ 
  var inner = this;
  return function (arg) { return inner(arg, callback); }
}

and:

function getData(url, callback) {
    ...
}

Let's analyze the types of each function:

  • getData is (string, function(argument, ...)) → null.
  • function(argument, function).then is (function(argument, ...)) → function(argument).

That's the core of the problem. When you do:

getData.then(function (argument) {}) it actually returns a function with the type function(argument). That's why .then can't be called onto it, because .then expects to be called onto a function(argument, function) type.

What you want to do, is wrap the callback function. (In the case of getData.then(parseData).then(f), you want to wrap parseData with f, not the result of getData.then(parseData).

Here's my solution:

Function.prototype.setCallback = function (c) { this.callback = c; }
Function.prototype.getCallback = function () { return this.callback; }

Function.prototype.then = function (f) {
  var ff = this;
  var outer = function () {
     var callback = outer.getCallback();
     return ff.apply(null, [].slice.call(arguments).concat(callback));
  };

  if (this.getCallback() === undefined) {
    outer.setCallback(f);
  } else {
    outer.setCallback(ff.getCallback().then(f));
  }

  return outer;
}
like image 23
Antoine Catton Avatar answered Oct 15 '22 05:10

Antoine Catton