16

You have a prototype object Foo with two async method calls, bar and baz.

var bob = new Foo()

Foo.prototype.bar = function land(callback) {
  setTimeout(function() {
    callback()
    console.log('bar');
  }, 3000);
};

Foo.prototype.baz = function land(callback) {
  setTimeout(function() {
    callback()
    console.log('baz');
  }, 3000);
};

We want to do bob.bar().baz() and have it log "bar" and "baz" sequentially.

If you cannot modify the method calls (including passing in your callback function), how can you pass a default callback into these method calls?

Some ideas:

  1. Wrap "bob" with decorator (still fuzzy on how to implement, could use a small example)

  2. Modify constructor to assign default callback if none assigned (have not considered if this is possible or not)

  3. Use a generator wrapper that will continue to call next method until none are left?

Anthony Chung
  • 1,467
  • 2
  • 22
  • 44
  • Can you specify how you'd use promises? They occurred but not sure how they apply – Anthony Chung Aug 18 '16 at 23:24
  • Are you married to the `bob.bar().baz()` syntax? There are many ways to get the operation your describing but I can't think of anything that would work with that specific syntax. – Mike Aug 18 '16 at 23:31
  • Assume we are :( I figure we have to wrap it somehow – Anthony Chung Aug 18 '16 at 23:31
  • Use queues if the async code can't fail. Use promises if it can. – zzzzBov Aug 19 '16 at 03:30
  • Have a look at http://stackoverflow.com/q/36242757/1048572. And no, generators absolutely won't help you here – Bergi Aug 21 '16 at 10:57
  • Please have a look at this topic: http://stackoverflow.com/a/39029819 – Pierre-Yves O. Nov 29 '16 at 17:41
  • What about `async function land(cb) { return await new Promise((a,r) => {/* setTimeout, etc*/ accept(cb())})`? You can even pass accept to the callback and have the callback handle the promise – Werlious Sep 02 '20 at 08:29

3 Answers3

11

The more recommended way instead is to use promises as this is the community-wide practice to do async work.

We want to do bob.bar().baz() and have it log "bar" and "baz" sequentially.

Why would you want to do that just to achieve this bob.bar().baz() "syntax"? You could do it pretty neatly using the Promise API w/o additional efforts to make that syntax work that indeed increases code complexity reducing the actual readability.

So, you might want to consider using the promise-based approach like this. It offers much flexibility than what you would have achieved with your approach:

Foo.prototype.bar = function () {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve()
            console.log('bar');
        }, 3000);
    };
};

Foo.prototype.baz = function () {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve()
            console.log('baz');
        }, 3000);
    };
};

Now you'd do this to run them sequentially one after another:

var bob = new Foo();

bob.bar().then(function() {
   return bob.baz();
});

// If you're using ES2015+ you could even do:
bob.bar().then(() => bob.baz());

If you need to chain more functions you could simply do it:

bob.bar()
    .then(() => bob.baz())
    .then(() => bob.anotherBaz())
    .then(() => bob.somethingElse());  

Anyway, if you're not used to using promises you might want to read this

kabirbaidhya
  • 3,264
  • 3
  • 34
  • 59
11

Warning this isn't quite right yet. Ideally we'd subclass Promise and have proper then/catch functionality but there are some caveats with subclassing bluebird Promise. The idea is to store an internal array of promise generating functions, then when a Promise is waited on (then/await) serially wait on those promises.

const Promise = require('bluebird');

class Foo {
  constructor() {
    this.queue = [];
  }

  // promise generating function simply returns called pGen
  pFunc(i,pGen) {
    return pGen();
  }

  bar() {
    const _bar = () => {
      return new Promise( (resolve,reject) => {
        setTimeout( () => {
          console.log('bar',Date.now());
          resolve();
        },Math.random()*1000);
      })      
    }
    this.queue.push(_bar);
    return this;
  }

  baz() {
    const _baz = () => {
      return new Promise( (resolve,reject) => {
        setTimeout( () => {
          console.log('baz',Date.now());
          resolve();
        },Math.random()*1000);
      })      
    }
    this.queue.push(_baz);
    return this;
  }

  then(func) {
    return Promise.reduce(this.queue, this.pFunc, 0).then(func);
  }
}


const foo = new Foo();
foo.bar().baz().then( () => {
  console.log('done')
})

result:

messel@messels-MBP:~/Desktop/Dropbox/code/js/async-chain$ node index.js 
bar 1492082650917
baz 1492082651511
done
Mark Essel
  • 4,606
  • 2
  • 29
  • 47
2

If you want to avoid callback hell and keep your sanity ES6 promises are the most appropriate approach for the sake of functional programming. You just chain up your sequential asynchronous tasks in the asynchronous timeline just like working in a synchronous timeline.

In this particular case you just need to promisify your asynchronous functions. Assume that your asynch functions takes a data and a callback like asynch(data,myCallback). Let us assume that the callback is error first type.

Such as;

var myCallback = (error,result) => error ? doErrorAction(error)
                                         : doNormalAction(result)

When your asynch function is promisified, you will actually be returned a function which takes your data and returns a promise. You are expected to apply myCallback at the then stage. The return value of myCallback will then be passed to the next stage at where you can invoke another asynch function supplied with the return value of myCallback and this goes on and on as long as you need. So let's see how we shall implement this abstract to your workflow.

function Foo(){}

function promisify(fun){
  return (data) => new Promise((resolve,reject) => fun(data, (err,res) => err ? reject(err) : resolve(res)));
}

function myCallback(val) {
  console.log("hey..! i've got this:",val);
  return val;
}

var bob = new Foo();

Foo.prototype.bar = function land(value, callback) {
  setTimeout(function() {
    callback(false,value*2);  // no error returned but value doubled and supplied to callback
    console.log('bar');
  }, 1000);
};

Foo.prototype.baz = function land(value, callback) {
  setTimeout(function() {
    callback(false,value*2);  // no error returned but value doubled and supplied to callback
    console.log('baz');
  }, 1000);
};

Foo.prototype.bar = promisify(Foo.prototype.bar);
Foo.prototype.baz = promisify(Foo.prototype.baz);

bob.bar(1)
   .then(myCallback)
   .then(bob.baz)
   .then(myCallback)
Redu
  • 25,060
  • 6
  • 56
  • 76