0

Assuming I have three asynchronous functions to be chained together, func1 --> func2 --> func3, it seems there are two ways to do it.

Option 1: promisify the first function, func1p = Promise.promisify(func1), leave the other two alone, and then create a chain like this:

func1p
  .then(func2)
  .then(func3)
  .catch(err)

Option 2: promisify all three functions, func1p = Promise.promisify(func1), func2p = Promise.promisify(func2), func3p = Promise.promisify(func3) and then create a chain like this:

func1p
  .then(func2p)
  .then(func3p)
  .catch(err)

According to MDN Web Docs, the then function (always) returns a new promise. So it seems to me it's not necessary to promisify func2 and func3 because they would have already been 'promisified' by the then function of the func1p. In other words, option 1 should be enough.

I've tested with both options and both seemed to work and gave the same result to me. My concern, however, is if there's any difference in performance, overhead etc. between the two and if so, which one is better.

My questions:

  • Is it a good practice to always promisify all functions and so I should use option 2 instead, or it's the other way around?
  • If option 1 is preferred, is it still true if func2 and func3 are to be created from the beginning?

EDIT: Here are my code (I used Express JS and request-promise)

EDIT2: When I asked this question I assumed my functions are asynchronous and the question is about what I should do with them. I didn't expect it to turn into a discussion about whether my functions are actual asynchronous or not. Imho they may or may not be but there is always the possibility that they will be asynchronous the next time and when I'm not sure, I'd rather assume that they are to be safe. So just to be clear, I edited my code to indicate that func1, func2, func3 are indeed asynchronous in this context.

 function func1 (filePath1) {
    fs.readFile(filePath1, (err, data1) => {
        if (err) throw err;
        console.log(data1);
    });
 }

 function func2 (filePath2) {
    fs.readFile(filePath2, (err, data2) => {
        if (err) throw err;
        console.log(data2);
    });
 }     

 function func3 (filePath3) {
    fs.readFile(filePath3, (err, data3) => {
        if (err) throw err;
        console.log(data3);
    });
 }
ptrgreen
  • 103
  • 1
  • 7
  • 1
    No, you don't need to and should not promisify synchronous functions. But `Promise.promisify` doesn't work on them anyway? – Bergi Sep 10 '17 at 09:14
  • No, `then` does not "promisfy" its arguments. Asynchronous functions that take callbacks don't work - you need to promisify them. All asynchronous functions that you want to use with `then` *must* return promises. – Bergi Sep 10 '17 at 09:17
  • "*I've tested with both options and both seemed to work and gave the same result to me.*" - Please post the whole exact code of that. I can't think of any usual `Promise.promisify` function that would allow this. – Bergi Sep 10 '17 at 09:18
  • 1
    What do `func1`, `func2`, and `func3` look like? Are they standard Node-callback-style asynchronous functions? (Also: Are you using Bluebird? It's the first result for `Promise.promisify`...) – T.J. Crowder Sep 10 '17 at 09:18
  • May be the async functions `func2` and `func3` are returning promises by default. – Redu Sep 10 '17 at 09:35
  • @T.J. Crowder: My `func1` is to read in a request, validate it and create a new request to another service; `func2` is to get the response as a huge json file; `func3` is to parse that json file and create another response to be returned to the client. @Bergi: If `then` does not "promisify" its arguments then what should I understand about a `then` function always returns another promise, as stated in the MDN link? @Redu: No, they are just standard function. – ptrgreen Sep 10 '17 at 09:49
  • @ptrgreen: You didn't answer my questions. – T.J. Crowder Sep 10 '17 at 09:50
  • @T.J. Crowder: I'm new to Javascript and may be wrong, and not entirely sure what do you mean by "standard Node-callback-style asynchronous functions", but my understanding is `func1`, `func2` and `func3` are asynchronous, in that I mean I usually would put `func2` as the callback of `func1`, and `func3` as the callback of `func2`, to make sure that they will be executed in order, as there's no guarantee that they will wait for each other to be completed first? – ptrgreen Sep 10 '17 at 09:57
  • @ptrgreen Not sure whether you understand [what "promisify" means](https://stackoverflow.com/q/22519784/1048572)? `then` always does return a promise [that is resolved with the return value of the callback](https://stackoverflow.com/a/22562045/1048572), but it has no idea about "promisification". – Bergi Sep 10 '17 at 09:59
  • Just to be clear, all `func1`, `func2` and `func3` are my own functions, they are not built in Node.js functions. – ptrgreen Sep 10 '17 at 09:59
  • @ptrgreen Again, please [edit] your question to post the code of these functions! And tell us what library with a `Promise.promisify` function you are using. – Bergi Sep 10 '17 at 09:59
  • @ptrgreen: Then post them, as you've been asked to do at least twice, and actually answer the questions you're asked. Again: Are you using Bluebird? If not, what's providing `Promise.promisify`? – T.J. Crowder Sep 10 '17 at 10:00
  • @ptrgreen: You have two of the all-time top 10 users in the [javascript](https://stackoverflow.com/tags/javascript/topusers) and [promise](https://stackoverflow.com/tags/promise/topusers) tags here actively trying to help you, and yet you keep disappearing, and when you reappear you don't answer the questions asked or provide the information requested for us to help you. This is not a great recipe for getting help with your question. – T.J. Crowder Sep 10 '17 at 10:21
  • @Bergi : please see the code I've added to the OP. – ptrgreen Sep 10 '17 at 10:50
  • @T.J. Crowder I could not just copy and paste as the real code which is part of a huge framework and involves a bunch of other dependencies, so I had to come up with something simpler and independent but still retains the same logic and did it as fast as I can. I greatly appreciate the support of the community. – ptrgreen Sep 10 '17 at 10:51
  • @ptrgreen: None of the three functions you've added in your edit does anything asynchronous. – T.J. Crowder Sep 10 '17 at 10:52
  • @Bergi: Thanks for links in your previous comment. I understand that the `then` method does not exactly 'promisify' its argument, hence the quotes. However I only need it to 'return a promise that is resolved with the return value of the callback', as you said, hence my question about whether I need to 'fully' promisify my functions or what the `then` method provides is already enough. – ptrgreen Sep 10 '17 at 10:55
  • @T.J. Crowder thanks for the confirmation. I guess it means it does not matter if I use promise or not in that case? However assume that `func1`, `func2` and `func3` are asynchronous, e.g. they are to read in file1, fil2, file3, is my question relevant? Which option should I go with? – ptrgreen Sep 10 '17 at 11:03
  • @ptrgreen `then` totally works with synchronous functions that directly `return` their result just fine. – Bergi Sep 10 '17 at 11:59
  • @Bergi: When I asked this question I assumed my functions are asynchronous and the question is about what I should do with them. I didn't expect it to turn into a discussion about whether my functions are actually asynchronous or not. So just to be clear, I edited my code to indicate that func1, func2, func3 are truly asynchronous in this context. I also noted that I understand the `then` method does not exactly promisify its arg, but I don't know if it's enough or not. – ptrgreen Sep 10 '17 at 12:47
  • @T.J.Crowder: I've edited my question again to make it clearer what I'd like to ask. – ptrgreen Sep 10 '17 at 12:50
  • 2
    @ptrgreen [Write your asynchronous functions so that they return promises](https://stackoverflow.com/a/25756564/1048572). Always. Then they also will work with `then` out of the box. The currently posted functions don't work at all. – Bergi Sep 10 '17 at 13:08
  • Thanks @Bergi, it's exactly what I'd like to know. – ptrgreen Sep 10 '17 at 14:07
  • 1
    In the above examples of func1, func2,func3, You wouldn't even need to do anything special, if you used a promise version of readFile. eg. your could have just done `return promReadFile()` or similar. Basically, the more functions that become promise compatible, the better things become. – Keith Sep 10 '17 at 14:20

1 Answers1

3

The question has become a bit broad because you're still learning a lot of this stuff. :-) Here are the three parts I see that I think I can now reasonably answer:

  1. When should I use Promise.promisify vs. just using then? What do they do differently from one another?

  2. Can I use synchronous functions with then?

  3. How should I write asynchronous functions so that I can use them with promises?

1. When should I use Promise.promisify vs. just using then? What do they do differently from one another?

Promise.promisify is a function provided by Bluebird and is not part of the standard JavaScript Promise object. Its job is to create a wrapper function around a standard Node-callback-style function* that provides a promise instead. It accepts a function and returns a new function wrapped around it.

then is a standard feature of promises that hooks up a callback to the promise's resolution (and optionally a handler to its rejection). It accepts a function and returns a new promise that will resolve or reject depending on what the function you give it does.

They're completely unrelated, other than both involving promises. They do completely different things. The only reason for using Promise.promisify is if you have to deal with legacy Node-style-callback functions (like those in the majority of the Node API, since it predates promises) and want to use promises with them. In contrast, you use then any time you use promises.

2. Can I use synchronous functions with then?

Yes. There's no particular reason to, but you can. The function you pass then gets called with the resolution value of the promise (or its rejection value if you pass the callback as the second argument) as its only argument. If your then callback returns a promise, then makes the promise it creates resolve or reject based on the promise the function returns; if the then callback returns a non-promise value, the promise then creates is resolved with that value.

3. How should I write asynchronous functions so that I can use them with promises?

Make your function return a promise.

Looking at your func1, for instance, it doesn't return a promise and in fact it doesn't work properly:

// Your `func1` from the question
function func1 (filePath1) {
    fs.readFile(filePath1, (err, data1) => {
        if (err) throw err;
        console.log(data1);
    });
}

The reason it doesn't work properly is that it throws from the readFile callback. But throws in that callback are not handled by anything usefully.

To write that function to be used with promises, you'd have it return a promise. You can do that by writing it like this:

// `func1` updated to use promises, and also to accept options
function func1(filePath1, options) {
    return new Promise((resolve, reject) => {
        fs.readFile(filePath1, options, (err, data1) => {
            if (err) {
                reject(err);
            } else {
                resolve(data1);
            }
        });
    });
}

...or, if you're using Bluebird, by simply using Promise.promisify, since all your function does is call readFile:

const func1 = Promise.promisify(fs.readFile);

If you're not using Bluebird, or if you want to promisify entire APIs (like the whole of fs) in one go, you might look at the promisify package instead.


* A standard "Node-callback-style function" is one that

  1. Accepts arguments where the last in the list is a callback function
  2. oes its work asynchronously, then
  3. Calls the callback with an initial argument which is either an error or null, followed (if it's null) by the result of the asynchronous call.

Let's look at an example: fs.readFile: It accepts the path of the file to read, an optional options object, and as the last argument a callback to call with the results. When it calls the callback, if there was an error, it passes that error as the first argument and no second argument at all. If there wasn't an error, it calls the callback with two arguments: null, and the file data.

So there's a pattern there:

  • The last argument to the API function is the callback
  • The first argument to the callback is an error or null

Promise.promisify puts a promise-enabled wrapper around any function that works in that way.

So: When should you use it? Any time you want to use promises with a Node-callback-style function.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Many thanks for the detailed answer, which cleared my mind a lot. – ptrgreen Sep 11 '17 at 00:06
  • For those also having the same question just popping up in my mind, if you need to call a synchronous function as part of a promise chain, you can just pass it straight to the `then` method and [should not attempt to promisify or make it return a promise](https://stackoverflow.com/questions/30159316/q-promisify-synchronous-operations-for-chaining-promises). – ptrgreen Sep 11 '17 at 00:12