Jake Wilson
Jake Wilson

Reputation: 91183

Chain class methods with callbacks?

In Node.js, how do you chain class methods together, when it's best practice to use callbacks?

In my PHP days, I would typically do something like this:

class MyClass {

  function get() {
    // fetch some data from an API endpoint
    return this;
  }

  function set(property, value) {
    // Set the property to some value on the object
    return this;
  }

  function save() {
    // Save the data to database
    return this;
  }
}

$myObject = new MyClass();
$myObject->set('something','taco')->save();

This very common OO approach allowed you to chain methods together as many times as you wanted.

When working with Node.js, can you still somehow do chaining similar to this? Or do you just end up in callback hell? Every single "chain" is a nested callback?

Or do I simply need to wrap my script in Promises?

new Promise(function(resolve, reject){
  var myObject = new MyClass();
  myObject.set('something','taco');
  resolve(myObject);
}).then(function(myObject){
  myObject.save();
});

Is that how you are supposed to do it? Is there any way to integrate this more deeply into my class so I don't have to wrap it in promises every time? I've seen some libraries have sort of a "promise-mode" like https://github.com/sindresorhus/got but after looking at the code I'm still not exactly sure how they did it.

Upvotes: 4

Views: 1159

Answers (3)

Behrouz Seyedi
Behrouz Seyedi

Reputation: 316

Your method could be something like this:

(async function() {
  class MyClass {
    constructor() {
      this._lastPromise = Promise.resolve();
      this.a = 0;
    }
    set(property, value) {
      var self = this;
      return new Promise(async (resolve, reject) => {
        console.log("self.%s set to: %s", property, value);
        await new Promise((resolve, reject) => {
          setTimeout(resolve, 5000);
        });
        self[property] = value;
        resolve(self);
      });
    }
    save() {
      var self = this;
      return new Promise(function(resolve, reject) {
        console.log(self.a + " Saved");
        resolve(self);
      });
    }
  }
  var myObject = new MyClass();
  console.log("before");
  await myObject
    .set("a", "1")
    .then(self => self.save())
    .then(self => self.set("a", "5"))
    .then(self => self.save());
  console.log("after");
})();

Upvotes: 0

laggingreflex
laggingreflex

Reputation: 34627

You can store a special private member variable __lastPromise. Initially it will be resolved by default. But then any function that does a task will update it with a return promise. And also the function itself will only do its task after the previous promise stored has been resolved.

Like this:

save() {
       // only after __lastPromise has resolved
    var newPromise = __lastPromise.then(function() {
       // Do stuff here
    });

    // update __lastPromise with the newly returned promise 
    this.__lastPromise = newPromise;

    // return this for regular chaining
    return this;
}

Complete class:

class MyClass {
    constructor(){
        this.__lastPromise = Promise.resolve();
        this.a = 0;
    }
    set(property, value) {
        var self = this;
        self.__lastPromise = self.__lastPromise.then(function() {
            return new Promise(function(resolve, reject) {
                console.log('self.%s set to: ', property, value);
                self[property] = value;
                resolve();
            });
        });
        return self;
    }
    save() {
        var self = this;
        self.__lastPromise = self.__lastPromise.then(function() {
            return new Promise(function(resolve, reject) {
                console.log('Saved');
                resolve();
            });
        });
        return self;
    }
}
var myObject = new MyClass();
myObject.set('a', '1').save().set('a', '2').save();
this.a set to:  1
Saved
this.a set to:  2
Saved

Upvotes: 1

TbWill4321
TbWill4321

Reputation: 8666

You chain using return this for synchronous calls.

You chain using Promise or callbacks for asynchronous calls.

class MyClass {
  
  get() {
    return new Promise((resolve, reject)=> {
      // Make Request and call resolve( result ) or reject( err )
    });
  }
  
  set( property, value ) {
    this[property] = value;
    return this;
  }
  
  save() {
    return new Promise((resolve, reject)=> {
      // Make Request and call resolve( result ) or reject( err )
    });
  }
}

var myObject = new MyClass();

myObject
  .set('something', 'taco')
  .save()
  // resolve handler
  .then(( saveResult )=> {
    console.log( saveResult );
    return myObject.get();
  })
  // resolve handler
  .then(( getResult )=> {
    console.log( getResult );
  })
  // reject handler
  .catch(( error )=> {  });

Upvotes: 4

Related Questions