Eirik Langholm Vullum PRO
JavaScript fanatic that loves node.js and React.
https://slides.com/eiriklv/taming-async-talk
https://github.com/eiriklv/taming-async-talk
twitter @eiriklv
Asynchronous JavaScript can
step(result, function(err, result) {
  step(result, function(err, result) {
    step(result, function(err, result) {
      step(result, function(err, result) {
        step(result, function(err, result) {
          step(result, function(err, result) {
            step(result, function(err, result) {
              step(result, function(err, result) {
                step(result, function(err, result) {
                  step(result, function(err, result) {
                    step(result, function(err, result) {
                      step(result, function(err, result) {
                        step(result, function(err, result) {
                          step(result, function(err, result) {
                            console.log('woopwoop!');
                            
                          })
                        })
                      })
                    })
                  })
                })
              })
            })
          })
        })
      })
    })
  })
})
We have a contrived express.js route that:
app.get('/', function(req, res) {  
  let inputFile = 'input.txt';
  try {
    let inputData = fs.readFileSync(inputFile);
    let processedData1 = process1Sync(inputData);
    let processedData2 = process2Sync(processedData1);
    let result = process3Sync(processedData2);
    
    res.status(200).send(result);
  } catch (err) {
    res.status(500).send(err);
  }
});
function process1Sync(input) {
  var start = Date.now();
  var len = 100;
  while (Date.now() < (start + len)) {}
  return input + ' - processed';
};
(nesting callbacks)
app.get('/', function(req, res) {
  let inputFile = 'input.txt';
  fs.readFile(inputFile, function(err, inputData) {
    if (err) return res.status(500).send(err);
    process1(inputData, function(err, processedData1) {
      if (err) return res.status(500).send(err);
      process2(processedData1, function(err, processedData2) {
        if (err) return res.status(500).send(err);
        process3(processedData2, function(err, result) {
          if (err) return res.status(500).send(err);
          
          res.status(200).send(result);
        });
      });
    });
  });
});function process1(input, callback) {
  let output = input + ' - processed';
  setTimeout(callback.bind(null, null, input), 100);
};
(separating each step of the flow)
app.get('/', function(req, res) {
  let inputFile = 'input.txt';
  fs.readFile(inputFile, onReadFile);
  function onReadFile(err, inputData) {
    if (err) return res.status(500).send(err);
    process1(inputData, onProcess1);
  }
  function onProcess1(err, processedData1) {
    if (err) return res.status(500).send(err);
    process2(processedData1, onProcess2);
  }
  function onProcess2(err, processedData2) {
    if (err) return res.status(500).send(err);
    process3(processedData2, onDone);
  }
  function onDone(err, result) {
    if (err) return res.status(500).send(err);
    res.status(200).send(result);
  }
});app.get('/', function(req, res) {
  let inputFile = 'input.txt';
  async.waterfall([
    function(callback) {
      fs.readFile(inputFile, function(err, inputData) {
        callback(err, inputData);
      });
    },
    function(inputData, callback) {
      process1(inputData, function(err, processedData1) {
        callback(err, processedData1);
      });
    },
    function(processedData1, callback) {
      process2(processedData1, function(err, processedData2) {
        callback(err, processedData2);
      });
    },
    function(processedData2, callback) {
      process3(processedData2, function(err, processedData3) {
        callback(err, processedData3);
      });
    }
  ], function(err, result) {
    if (err) return res.status(500).send(err);
    res.status(200).send(result);
  });
});app.get('/', function(req, res) {
  let inputFile = 'input.txt';
  async.waterfall([
    fs.readFile.bind(fs, inputFile),
    process1,
    process2,
    process3
  ], function(err, result) {
    if (err) return res.status(500).send(err);
    res.status(200).send(result);
  });
});app.get('/', function(req, res) {
  let inputFile = 'input.txt';
  let flow = [
    fs.readFile.bind(fs, inputFile),
    process1,
    process2,
    process3
  ];
  let done = function(err, result) {
    if (err) return res.status(500).send(err);
    res.status(200).send(result);
  };
  async.waterfall(flow, done);
});app.get('/', function(req, res) {
  let inputFile = 'input.txt';
  let flow = async.compose(
    process3,
    process2,
    process1,
    fs.readFile.bind(fs, inputFile)
  );
  let done = function(err, result) {
    if (err) return res.status(500).send(err);
    res.status(200).send(result);
  };
  flow(done);
});app.get('/', function(req, res) {
  let inputFile = 'input.txt';
  promisify(fs.readFile.bind(fs))(inputFile)
    .then(promisify(process1))
    .then(promisify(process2))
    .then(promisify(process3))
    .then(function(result) {
      res.status(200).send(result);
    })
    .catch(function(err) {
      res.status(500).send(err);
    });
});function promisify(fn) {
  return function() {
    let self = this;
    let args = Array.prototype.slice.call(arguments);
    return new Promise(function(resolve, reject) {
      let callback = function(err, result) {
        if (err) return reject(err);
        resolve(result);
      };
      fn.apply(self, args.concat(callback));
    });
  };
}app.get('/', function(req, res) {
  let inputFile = 'input.txt';
  fs.readFileAsync(inputFile)
    .then(Promise.promisify(process1))
    .then(Promise.promisify(process2))
    .then(Promise.promisify(process3))
    .then(function(result) {
      res.status(200).send(result);
    })
    .catch(function(err) {
      res.status(500).send(err);
    });
});const fs = Promise.promisifyAll(require('fs'));
// fs.readFile -> fs.readFileAsync
// fs.writeFile -> fs.writeFileAsync
// fs.appendFile -> fs.appendFileAsync
// ...app.get('/', function(req, res) {
  let inputFile = 'input.txt';
  let data = hl([inputFile]);
  data
    .flatMap(hl.wrapCallback(fs.readFile.bind(fs)))
    .flatMap(hl.wrapCallback(process1))
    .flatMap(hl.wrapCallback(process2))
    .flatMap(hl.wrapCallback(process3))
    .stopOnError(function(err) {
      res.status(500).send(err);
    })
    .apply(function(result) {
      res.status(200).send(result);
    });
});data
  .flatMap(hl.wrapCallback(
    async.compose(
      process3,
      process2,
      process1,
      fs.readFile.bind(fs)
    )
  ))
  .stopOnError(handleError)
  .apply(handleSuccess);function *fooGen() {
  var x = yield 'bar';
  var y = yield 'baz';
  console.log('result: ', x + y);
}
var fooGenIterator = fooGen();
fooGenIterator.next();   // { value: 'bar', done: false }
fooGenIterator.next(10); // { value: 'baz', done: false }
fooGenIterator.next(10); // { value: undefined, done: true }
> result: 23
app.get('/', function(req, res) {  
  run(function *() {
    let inputFile = 'input.txt';
    try {
      let inputData = yield fs.readFile.bind(fs, inputFile);
      let processedData1 = yield process1.bind(null, inputData);
      let processedData2 = yield process2.bind(null, processedData1);
      let result = yield process3.bind(null, processedData2);
      res.status(200).send(result);
    } catch (err) {
      res.status(500).send(err);
    }
  });
});function run(fn) {
  let gen = fn();
  function next(err, res) {
    if (err) return gen.throw(err);
    let ret = gen.next(res);
    if (ret.done) return;
    ret.value(next);
  }
  next();
};app.get('/', function(req, res) {  
  run(function *() {
    let inputFile = 'input.txt';
    try {
      let inputData = yield curry(fs.readFile.bind(fs))(inputFile);
      let processedData1 = yield curry(process1)(inputData);
      let processedData2 = yield curry(process2)(processedData1);
      let result = yield curry(process3)(processedData2);
      
      res.status(200).send(result);
    } catch (err) {
      res.status(500).send(err);
    }
  });
});function curry(fn) {
  let args = Array.prototype.slice.call(arguments, 1);
  return (function curryFn(prevArgs) {
    return function(arg) {
      let totalArgs = prevArgs.concat(Array.prototype.slice.call(arguments));
      if (!arg) totalArgs.push(undefined);
      if (totalArgs.length < fn.length) {
        return curryFn(totalArgs);
      } else {
        return fn.apply(null, totalArgs);
      }
    };
  }(args));
};app.get('/', function(req, res) {  
  run(function *() {
    let inputFile = 'input.txt';
    try {
      let inputData = yield promisify(fs.readFile.bind(fs))(inputFile);
      let processedData1 = yield promisify(process1)(inputData);
      let processedData2 = yield promisify(process2)(processedData1);
      let result = yield promisify(process3)(processedData2);
      
      res.status(200).send(result);
    } catch (err) {
      res.status(500).send(err);
    }
  });
});function run(fn) {
  let gen = fn();
  function next(err, res) {
    if (err) return gen.throw(err);
    let ret = gen.next(res);
    if (ret.done) return;
    if (typeof ret.value.then === 'function') {
      try {
        ret.value.then(function(value) {
          next(null, value);
        }, next);
      } catch (e) {
        gen.throw(e);
      }
    } else {
      try {
        ret.value(next);
      } catch (e) {
        gen.throw(e);
      }
    }
  }
  next();
}const co = require('co');app.get('/', function(req, res) {  
  co(function *() {
    let inputFile = 'input.txt';
    try {
      let inputData = yield promisify(fs.readFile.bind(fs))(inputFile);
      let processedData1 = yield promisify(process1)(inputData);
      let processedData2 = yield promisify(process2)(processedData1);
      let result = yield promisify(process3)(processedData2);
      res.status(200).send(result);
    } catch (err) {
      res.status(500).send(err);
    }
  }).catch(function(err) {...});
});const readFile = promisify(fs.readFile.bind(fs));
const process1 = promisify(require('./process1'));
const process2 = promisify(require('./process2'));
const process3 = promisify(require('./process3'));app.get('/', function(req, res) {  
  co(function *() {
    let inputFile = 'input.txt';
    try {
      let inputData = yield readFile(inputFile);
      let processedData1 = yield process1(inputData);
      let processedData2 = yield process2(processedData1);
      let result = yield process3(processedData2);
      
      res.status(200).send(result);
    } catch (err) {
      res.status(500).send(err);
    }
  }).catch(function(err) {...});
});// shim native modules with promises
const fs = require('mz/fs');
// use .promisify(..) / .promisifyAll(..)
// to shim user-libs
const Promise = require('bluebird');app.get('/', function(req, res) {  
  co(function *() {
    let inputFile = 'input.txt';
    try {
      let inputData = yield fs.readFile(inputFile);
      let processedData1 = yield process1(inputData);
      let processedData2 = yield process2(processedData1);
      let result = yield process3(processedData2);
      
      res.status(200).send(result);
    } catch (err) {
      res.status(500).send(err);
    }
  }).catch(function(err) {...});
});app.post('/process-file', function(req, res) {
  fibrous.run(function() {
    var inputFile = 'input.txt';
    var outputFile = 'output.txt';
    try {
      var inputData = fs.sync.readFile(inputFile);
      var processedData1 = process1.sync(inputData);
      var processedData2 = process2.sync(processedData1);
      var processedData3 = process3.sync(processedData2);
      fs.sync.writeFile(outputFile, processedData3);
      
      res.status(200).send('success');
    } catch (err) {
      res.status(500).send(err);
    }
  }, function(err) {...});
});ES7 async await
http://jakearchibald.com/2014/es7-async-functions/
CSP (Communicating Sequencial Processes)
https://github.com/ubolonton/js-csp
https://www.youtube.com/watch?v=W2DgDNQZOwo
Generators (and io.js)
http://davidwalsh.name/es6-generators
By Eirik Langholm Vullum
Talk for Node.js Oslo Meetup