I would like to use util.promisify to promisify custom functions. However, util.promisify works for node-style methods that have an error-first callback as the last argument. How can I adjust my custom functions so that they will work with util.promisify?
-
Well, update the signature of your functions, to fit that specification. Ie add an "error-first callback" as last parameter to your function. – derpirscher Jan 04 '22 at 17:32
1 Answers
How can I adjust my custom functions so that they will work with util.promisify?
My first choice would be to just add a new API that returns a promise - that wraps your own API inside a new Promise constructor. Then, you have a documented, promise-returning API that is obvious how to use and requires no extra steps by the client to use it.
But, if you really want to make something compatible with util.promisify()
, read on...
If you had shown the calling convention for your function that you want util.promisify()
to work with, we could have offered a custom implementation specifically for that code, but since you didn't here's an example implementation below.
The general concept is described here in the doc, though I would not exactly say the doc is very clear about how things work. Here's an example.
Suppose you have a timer function that takes three arguments in this order:
timer(callback, t, v)
where t
is the time in ms for the timer and v
is the value it will pass to the callback when the timer fires. And, it passes the callback two values callback(v, err)
in that order. And, yes, this particular timer can sometimes communicate back errors.
Note, specifically that the callback is passed as the first argument and the callback itself gets err
as the second argument, both of which are obviously incompatible with the default implementation of util.promisify()
(for purposes of this demonstration) as they don't match the nodejs asynchronous calling convention.
Here's an example usage of this timer (non-promisified) function that does NOT follow the nodejs calling convention:
// example
timer((v, err) => {
console.log(v, err); // outputs "hi", null
}, 2000, "hi");
To make it compatible with util.promisify()
, we can create a custom promisify behavior like this:
// define promise returning implementation of timer that leaves out
// the callback argument, but takes the same other arguments
timer[util.promisify.custom] = (t, v) => {
return new Promise((resolve, reject) => {
timer((value, err) => {
if (err) {
reject(err);
} else {
resolve(value);
}
}, t, v);
});
};
The general concept is that when you pass a function reference to util.promisify(fn)
, it looks for fn[util.promisify.custom]
and, if it exists, it will use that promisify implementation instead of its default one. If you're wondering what util.promisify.custom
is, it's a symbol defined by the util
library - just a unique property name that you can assign to your own function as a property in order to identify that you have implemented a custom promisify capability. Since symbols are unique within any nodejs implementation, adding that property to your function can't interfere with any other properties.
Which can then be used like this by other callers:
const timerP = util.promisify(timer);
// you call timerP, leaving out the callback argument
timerP(3000, "bye").then(result => {
console.log(result); // outputs "bye" after 3000ms
}).catch(err => {
console.log(err);
});
If you want to see the nodejs implementation for util.promisify()
, you can see it here in the actual util.js code.

- 683,504
- 96
- 985
- 979
-
1If you're going to use the Promise constructor anyway I'd just use the Promise constructor to promisify my functions instead of util.promisify – slebetman Jan 05 '22 at 07:38
-
@slebetman - The point of this custom capability is if you want to make a non-conforming function in your library work with `util.promisify()` for others to use without surfacing and documenting your own promise-returning API. So, the Promise constructor implementation can be done once in the library and others can then use it via `util.promisify()` rather than every client having to implement their own Promise constuctor implementation. It's not something I'd do in my own library because I'd just offer a promise-return API on its own. But, it's what the OP asked how to do. – jfriend00 Jan 05 '22 at 07:41
-
The problem with that is that as a javascript user I don't expect functions outside of node built-in library to work with `util.promisify`. So the only real use of this is to be able to write in your documentation that your module works with `util.promisify` but that takes more words to document than simply saying that the promise version of the module is also available. Either way this takes way more effort than just using the Promise constructor. – slebetman Jan 05 '22 at 07:44
-
@slebetman - In light of your first comment, I added a preface to my answer. – jfriend00 Jan 05 '22 at 07:49
-
@slebetman - I don't disagree that it's better to just create a promise-returning version of your API that doesn't take an extra promisify step to use and can be clearly documented. But, the OP asked how to make something work with `util.promisify()` so that's what I've demonstrated. – jfriend00 Jan 05 '22 at 07:50