Alexander Mills
Alexander Mills

Reputation: 99960

Resolving a Promise without calling the 'then'

I have this code that is part of a small API that I am writing for an NPM module called Poolio. The question I have seems to be a common question for those supporting error-first callbacks as well as promises- how do we support both while maintaining consisent APIs and consistent return values from the API? For example, if I conditionally return a promise from my API, depending on whether the consumer of my lib provides a callback, that is a little awkward in my opinion.

The consumer of the lib can provide a callback or use the Promise then function, but not both.

Here is a function exported by my lib, that I would like to promisify:

Pool.prototype.any = function (msg, cb) {

    var workId = this.counter++;
    var self = this;

    return new Promise(function (resolve, reject) {

        if (typeof cb === 'function') {
            self.resolutions.push({
                workId: workId,
                cb: cb
            });
        }
        else {
            self.resolutions.push({
                workId: workId,
                resolve: resolve,
                reject: reject
            });
        }

        if (this.available.length > 0) {
            var cp = this.available.shift();
            cp.workId = workId;
            cp.send(msg);
        }
        else {
            self.msgQueue.push({
                workId: workId,
                msg: msg
            });
        }
    });

};

my question is - if the user provides a callback function in the original function arguments, how can I resolve the promise without calling 'then'? Sorry it's hard to explain but hopefully you can understand.

also there is this interesting question: Do never resolved promises cause memory leak?

Upvotes: 6

Views: 12564

Answers (3)

Teocci
Teocci

Reputation: 8835

I think you are trying to do something like this:

function Pool() {
  this.counter = 0
  this.resolutions = []
  this.available = []
  this.msgQueue = []
}

Pool.prototype.any = function(msg, cb) {
  const self = this;

  const regCB = (msg, resolve, reject) => {
    const workId = self.counter++;
    self.resolutions.push({
      workId,
      resolve,
      reject,
    })

    if (self.available.length > 0) {
      const cp = self.available.shift();
      cp.workId = workId;
      cp.send(msg);
    } else {
      self.msgQueue.push({
        workId,
        msg,
      });
    }
  }

  const promise = new Promise((resolve, reject) => {
    if (typeof cb === 'function') {
      resolve = data => cb(null, data)
      reject = err => cb(err, null)
    }

    regCB(msg, resolve, reject)
    resolve(msg)
  })


  return promise
}

const fnc = (err, data) => {
  console.log('fnc', {
    data
  })
}

const promise = Promise.resolve(3)
const pool = new Pool
pool.any('with_function', fnc)

const poolPromise = pool.any('with_promise')
poolPromise.then((data) => {
  console.log('poolPromise', {
    data
  })
})

Upvotes: 0

Shanoor
Shanoor

Reputation: 13662

Actually, it's a pretty common thing APIs do (mongodb-driver example). Basically, write a private function accepting a callback, write a public function checking for cb and writing it if necessary. Using the code from your github (_any might need a refactoring, you don't need to check if cb is a function for example and maybe other things too):

 // private function
var _any = function(msg, cb) {
  if (this.kill) {
    console.log('warning: pool.any called on pool of dead/dying workers');
    return;
  }

  debug('current available pool size for pool_id ' + this.pool_id + ' is: ' + this.available.length);
  var workId = this.counter++;

  if (typeof cb === 'function') {
    this.resolutions.push({
      workId: workId,
      cb: cb
    });
  } else {
    workId = -1;
  }

  if (this.available.length > 0) {
    var cp = this.available.shift();
    cp.workId = workId;
    cp.send(msg);
  } else {
    this.msgQueue.push({
      workId: workId,
      msg: msg
    });
  }
};

 // public exposed function
Pool.prototype.any = function(msg, cb) {
  if (typeof cb === 'function') {
    // cb is provided, no action is required here
    return _any(msg, cb);
  } 

  // no cb, wrap the call inside a Promise and provide a cb
  return new Promise(function(resolve, reject) {
    _any(msg, function(err, data) {
      if (err) reject(err);
      else resolve(data);
    });
  });
}

Upvotes: 1

slebetman
slebetman

Reputation: 113876

It's actually very straightforward. Only you may have missed it because it's hidden amongst that tangle of code.

Basically you do this:

var promise = new Promise(function (resolve, reject) { /*....*/});

if (typeof cb === 'function') {
    promise.then(cb);
} else {
    return promise;
}

Upvotes: 2

Related Questions