2

I am confused on this one because every tutorial I have found so far assumes I can edit the library code, or that the library only has call backs or the call back as the last parameter.

The library I am using has every function set up like function(successCallBack(result), FailCallBack(error),options)

So in each instance, I end up using code like

var options={stuff1:1, stuff2:2};

doStuff(success,failure,options);

function success(result){
    //handle, usually with another call to a similar function, chaining callbacks together
};

function failure(error){
     //handle error
};

How do I convert these into promises when I only have control of the call, and the success and failures?

Also, as a bonus, the chains are accessing variables outside them.

var options={stuff1:1, stuff2:2};

doStuff(success,failure,options);

function success(result){
    var options2={stuff1:1, stuff2:2};
    doStuff2(function(result2){
                processStuff(result1, result2);
            },function(error){
                 //handle error
            },options2)
};

function failure(error){
     //handle error
};

function processSuff(result1,result2){
    //do things to results
}

Thanks

Trevor
  • 2,792
  • 1
  • 30
  • 43
  • Working version of a "promisify" function that can be used to create a new function that "wraps" an existing function converting a traditional callback interface to a function returning a promise: https://stackoverflow.com/questions/26391419/trying-to-understand-how-promisification-works-with-bluebird/26391516#26391516. Also, the [Bluebird promise library](http://bluebirdjs.com/docs/api-reference.html) has both `.promisify()` and `.promisifyAll()`. – jfriend00 Aug 03 '17 at 20:50
  • These assume you have a function with a typical async calling convention of single callback as last argument. When you do not have that, you will have to adapt these to fit your calling convention. – jfriend00 Aug 03 '17 at 20:51
  • I have bluebird but I don't yet know how to use it properly – Trevor Aug 03 '17 at 20:52
  • Because your functions that take two callbacks and first and second arguments are non-standard for async calling, you can't use Bluebird right out of the box to promisify. You will have to create your own promise-returning wrapper for each function you want to use with promises. – jfriend00 Aug 03 '17 at 20:53
  • @jfriend00 ah thank you, that explains my confusion a bit better. – Trevor Aug 03 '17 at 21:00

2 Answers2

6

You could use the following function. It accepts a function to promisify and options, and returns promise:

let promisify = (fn, opts) => {
  return new Promise((resolve, reject) => {
    fn(resolve, reject, opts);
  });
}

It could be used as follows:

promisify(doStuff, options)
  .then(data => console.log('Success call', data))
  .catch(err => console.log('Error', err));
alexmac
  • 19,087
  • 7
  • 58
  • 69
  • in the ` .then(() => /* Success call*/)` how does it get the results? do I put the success(result) function call there? – Trevor Aug 03 '17 at 20:50
  • Updated the code, `then` callback will accept the parameter which will be passed to the original `success` function. – alexmac Aug 03 '17 at 20:53
  • Thank you. It will take me a little bit of time to test implement this, but this looks really good, if confusing to a javascript rookie like me. Can I use this to fix the variable scope piece where the last item in the chain reads the results of each chain? – Trevor Aug 03 '17 at 20:55
  • I don't quite understand what you mean. Could you provide more details? – alexmac Aug 06 '17 at 15:40
2

Rather than call promisify every time you want to use your function, you can wrap it once and get a new function that you can just use as needed. The new function will return a promise and resolve/reject instead of the success and fail callbacks. This particular version of promisify is specifically coded for the calling convention you show of fn(successCallback, errorCallback, options). If you have other functions with different calling conventions, then you can create a different promisify version for them:

// return a promisified function replacement for this specific
//    calling convention: fn(successCallback, errorCallback, options)
function promisify1(fn) {
    return function(options) {
        return new Promise((resolve, reject) => {
            fn(resolve, reject, options);
        });
    }
}

So, then you would use this like this:

// do this once, somewhere near where doStuff is imported
let doStuffPromise = promisify1(doStuff);

// then, anytime you would normally use doStuff(), you can just use
//    doStuffPromise() instead

doStuffPromise(options).then(results => {
    // process results here
}).catch(err => {
    // handle error here
});

The advantage of doing it this way is that you can "promisify" a given function of set of functions just once in your project and then just use the new promisified versions.


Suppose you imported an object that had a whole bunch of these types of interfaces on it. You could then make yourself a promisifyObj() function that would make a promisified interface for all the functions on the object.

// a "Promise" version of every method on this object
function promisifyAll(obj) {
    let props = Object.keys(obj);
    props.forEach(propName => {
        // only do this for properties that are functions
        let fn = obj[propName];
        if (typeof fn === "function") {
            obj[propName + "Promise"] = promisify1(fn);
        }
    });
}

So, if you had an object named myModule that had all these methods with this non-promise interface on it, you could promisify that module in one step:

promisifyAll(myModule);

And, then you could use any method from that object by just adding the "Promise" suffix to the method name:

myModule.someMethodPromise(options).then(results => {
    // process results here
}).catch(err => {
    // handle error here
});
jfriend00
  • 683,504
  • 96
  • 985
  • 979