5
\$\begingroup\$

I know that promises are the current recommended way to go for asynchronous chaining but, every time I use promises, I don't feel as comfortable as I wish. All that "then" statements and managing scopes always look a bit strange for me. So, this is the way I've been creating modules and chaining functions. I use a simple queue to chain the calls.

In my opinion, the code is more understandable, you don't need "then" statements, you can explicit change context if needed. It looks easier for me. I would like opinions and improvements suggestions, critics are welcome. Why should I use promise over this?

"use strict";

var pause = function(ms, callback) {
    setTimeout(function() {
        callback();
    }, ms);
};

var temp = {
    prop1: 'This is the temp prop1 property!'
};

var P = function() {
    let queue = [];
    let SCOPE = this;

    this.prop1 = 'Property';

    this.displayProperty = function(property) {
        var me = this;
        queue.push(function() {
            console.log(this[property]);
            me.resolve();
        });
        return this;
    }

    this.setProperty = function(property, data) {
        var me = this;
        queue.push(function() {
            this[property] = data;
            me.resolve();
        });
        return this;
    }

    this.asyncDisplayProperty = function(property, data) {
        var me = this;
        queue.push(function() {
            var scope = this;
            pause(3000, function() {
                console.log(scope[property]);
                me.resolve();
            });
        });
        return this;
    }

    this.resolve = function() {
        let fn = queue.shift();
        if (fn) {
            fn.apply(SCOPE, arguments);
        }
    }

    this.bind = function(s) {
        queue.push(() => {
            SCOPE = s;
            this.resolve();
        });
        return this;
    }

}

var foo = new P();

foo
    .displayProperty('prop1') //"Property"
    .asyncDisplayProperty('prop1') //"Property" after 3s
    .setProperty('prop1', 'changed property')
    .displayProperty('prop1') //"changed property"
    .bind(temp) //change the context
    .asyncDisplayProperty('prop1') //"This is the temp prop1 property!" after 3s
    .setProperty('prop1', 'changed temp property')
    .displayProperty('prop1') //"changed temp property"
    .resolve();

Just for the record, this is the promise version I've been using:

"use strict";

var temp = {
    prop1: 'This is the temp prop1 property!'
};

var P = function() {
    let CONTEXT = this;

    this.prop1 = 'Property';

    this.displayProperty = function(property) {
        var me = CONTEXT;
        return new Promise((resolve, reject) => {
            console.log(me[property]);
            resolve();
        });
    }

    this.setProperty = function(property, data) {
        var me = CONTEXT;
        return new Promise((resolve, reject) => {
            me[property] = data;
            resolve();
        });
    }

    this.asyncDisplayProperty = function(property, data) {
        var me = CONTEXT;
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log(me[property]);
                resolve();
            }, 3000)
        });
    }

    this.bind = function(s) {
        return new Promise((resolve, reject) => {
            CONTEXT = s;
            resolve();
        });
    }
};

var foo = new P();

Promise.resolve()
    .then(() => foo.asyncDisplayProperty('prop1'))
    .then(() => foo.asyncDisplayProperty('prop1'))
    .then(() => foo.setProperty('prop1', 'changed property'))
    .then(() => foo.displayProperty('prop1'))
    .then(() => foo.asyncDisplayProperty('prop1'))
    .then(() => foo.bind(temp))
    .then(() => foo.displayProperty('prop1'))
    .then(() => foo.setProperty('prop1', 'changed temp property'))
    .then(() => foo.displayProperty('prop1'));
\$\endgroup\$

4 Answers 4

4
\$\begingroup\$

I recommend using bluebird for promises; it has a ton of nice features that really improve quality of life and seems to be faster than the alternatives by quite a large margin. (Note: I only briefly looked over the JSPerf, but it seems to be done well enough.)

Using bluebird (and a few other adjustments), the promise version of the code can be simplifed to:

"use strict";

class PropertyItem {
    constructor(value) {
        this.value = value;
        this.log = this.log.bind(this);
        this.setValue = this.setValue.bind(this);
    }
    log() {
        console.log(this.value);
    }
    setValue(value) {
        this.value = value;
    }
}

const a = new PropertyItem('This is the temp prop1 property!');
const b = new PropertyItem('Property');

Bluebird
    .delay(3000)
    .tap(a.log)
    .delay(3000)
    .tap(a.log)
    .tap(() => a.setValue('changed property'))
    .tap(a.log)
    .delay(3000)
    .tap(a.log)
    .tap(b.log)
    .tap(() => b.setValue('changed temp property'))
    .tap(b.log);

This is the sort of approach I would recommend.

\$\endgroup\$
4
\$\begingroup\$

You're basically starting to rewriting promises. I think there are pros and cons:

For your solution:

  • Its a little simpler. I would have created a wrapper function around queue.push to make it clearer and easier to modify. You could also make this into a re-usable base class. Lastly, I'm not sure why you create and refer to me everywhere instead of just referring to SCOPE.
  • It's your own code so if it doesn't do what you want you can change it.
  • You don't need to wait/hope someone will implement a feature you need.

For promises:

  • Promises has more functionality (Promise.all, catch, race) and you can have multiple thens attached to a promise.
  • Thousand's of people have reviewed the Promise code for bugs.
  • Other developers will know what promises are but have to learn your code.
\$\endgroup\$
1
\$\begingroup\$

You've already gotten an answer to your direct question, but I noticed this and couldn't resist the drive by code review.

var pause = function(ms, callback) {
    setTimeout(function() {
        callback();
    }, ms);
};

callback is already a function, so why wrap it in another? Surely you can just pass it directly into setTimeout.

var pause = function(ms, callback) {
    setTimeout(callback, ms);
};

Now it's quite obvious that you've simply renamed setTimeout to pause. You can get rid of the pause function entirely.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Lol, you're right. I had other things attached to that piece of code, but left it that way when refactoring to post here. It makes no sense... thanks! \$\endgroup\$
    – Siegfried
    Mar 20, 2017 at 10:49
1
\$\begingroup\$

You could use async waterfall function if you don't like Promise

import waterfall from 'async/waterfall';

async.waterfall([
    function(callback) {
        callback(null, 'one', 'two');
    },
    function(arg1, arg2, callback) {
        // arg1 now equals 'one' and arg2 now equals 'two'
        callback(null, 'three');
    },
    function(arg1, callback) {
        // arg1 now equals 'three'
        callback(null, 'done');
    }
], function (err, result) {
    // result now equals 'done'
});

UPDATE :

I just found out a way to use promise waterfall without using third party libs:

this one use the array reduce to collect the promise function

var guid = 0;
function run() {
  guid++;
  var id = guid;
  return new Promise(resolve => {
    setTimeout(function () {
      console.log(id);
      resolve(id);
    }, (Math.random() * 1.5 | 0) * 1000);
  });
}

var promise = Array.from({ length: 10 }).reduce(function (acc) {
      return acc.then(function (res) {
        return run().then(function (result) {
          res.push(result);
          return res;
        });
      });
    }, Promise.resolve([]));

    promise.then(console.log);
\$\endgroup\$
2
  • \$\begingroup\$ Thanks, I know, Async, Bluebird, but I am looking for a solution without third party libs. \$\endgroup\$
    – Siegfried
    Mar 20, 2017 at 10:50
  • \$\begingroup\$ Yes bluebird also a good one, i prefer that too.. \$\endgroup\$
    – Ihsan
    Mar 22, 2017 at 4:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.