Skip to content

Commit 364e707

Browse files
committed
promisify
1 parent d912bce commit 364e707

File tree

9 files changed

+120
-2
lines changed

9 files changed

+120
-2
lines changed

1-js/11-async/07-promisify/article.md

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# Promisification
2+
3+
Promisification -- is a long word for a simple transform. It's conversion of a function that accepts a callback into a function returning a promise.
4+
5+
In other words, we create a wrapper-function that does the same, internally calling the original one, but returns a promise.
6+
7+
Such transforms are often needed in real-life, as many functions and libraries are callback-based. But promises are more convenient. So it makes sense to promisify those.
8+
9+
For instance, we have `loadScript(src, callback)` from the chapter <info:callbacks>.
10+
11+
```js run
12+
function loadScript(src, callback) {
13+
let script = document.createElement('script');
14+
script.src = src;
15+
16+
script.onload = () => callback(null, script);
17+
script.onerror = () => callback(new Error(`Script load error for ${src}`));
18+
19+
document.head.append(script);
20+
}
21+
22+
// usage:
23+
// loadScript('path/script.js', (err, script) => {...})
24+
```
25+
26+
Let's promisify it. The new `loadScriptPromise(src)` function will do the same, but accept only `src` (no callback) and return a promise.
27+
28+
```js
29+
let loadScriptPromise = function(src) {
30+
return new Promise((resolve, reject) => {
31+
loadScript(src, (err, script) => {
32+
if (err) reject(err)
33+
elsee resolve(script);
34+
});
35+
})
36+
}
37+
38+
// usage:
39+
// loadScriptPromise('path/script.js').then(...)
40+
```
41+
42+
Now `loadScriptPromise` fits well in our promise-based code.
43+
44+
As we can see, it delegates all the work to the original `loadScript`, providing its own callback that translates to promise `resolve/reject`.
45+
46+
As we may need to promisify many functions, it makes sense to use a helper.
47+
48+
That's actually very simple -- `promisify(f)` below takes a to-promisify function `f` and returns a wrapper function.
49+
50+
That wrapper does the same as in the code above: returns a promise and passes the call to the original `f`, tracking the result in a custom callback:
51+
52+
```js
53+
function promisify(f) {
54+
return function (...args) { // return a wrapper-function
55+
return new Promise((resolve, reject) => {
56+
function callback(err, result) { // our custom callback for f
57+
if (err) {
58+
return reject(err);
59+
} else {
60+
resolve(result);
61+
}
62+
}
63+
64+
args.push(callback); // append our custom callback to the end of arguments
65+
66+
f.call(this, ...args); // call the original function
67+
});
68+
};
69+
};
70+
71+
// usage:
72+
let loadScriptPromise = promisify(loadScript);
73+
loadScriptPromise(...).then(...);
74+
```
75+
76+
Here we assume that the original function expects a callback with two arguments `(err, result)`. That's what we meet most often. Then our custom callbacks is exactly in the right format, and `promisify` works great for such case.
77+
78+
But what is the original `f` expects a callback with more arguments `callback(err, res1, res2)`?
79+
80+
Here's a modification of `promisify` that returns an array of multiple callback results:
81+
82+
```js
83+
// promisify(f, true) to get array of results
84+
function promisify(f, manyArgs = false) {
85+
return function (...args) {
86+
return new Promise((resolve, reject) => {
87+
function *!*callback(err, ...results*/!*) { // our custom callback for f
88+
if (err) {
89+
return reject(err);
90+
} else {
91+
// resolve with all callback results if manyArgs is specified
92+
*!*resolve(manyArgs ? results : results[0]);*/!*
93+
}
94+
}
95+
96+
args.push(callback);
97+
98+
f.call(this, ...args);
99+
});
100+
};
101+
};
102+
103+
// usage:
104+
f = promisify(f, true);
105+
f(...).then(err => ..., arrayOfResults => ...)
106+
```
107+
108+
In some cases, `err` may be absent at all: `callback(result)`, or there's something exotic in the callback format, then we can promisify such functions manually.
109+
110+
There are also modules with a bit more flexible promisification functions, e.g. [es6-promisify](https://github.com/digitaldesignlabs/es6-promisify). In Node.js, there's a built-in `util.promisify` function for that.
111+
112+
```smart
113+
Promisification is a great approach, especially when you use `async/await` (see the next chapter), but not a total replacement for callbacks.
114+
115+
Remember, a promise may have only one result, but a callback may technically be called many times.
116+
117+
So promisification is only meant for functions that call the callback once. Furhter calls will be ignored.
118+
```

1-js/11-async/07-async-await/article.md renamed to 1-js/11-async/08-async-await/article.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ In case of an error, it propagates as usual: from the failed promise to `Promise
281281

282282
````
283283
284-
## Async/await versus other async actions
284+
## Timiing: async/await and higher-level actions
285285
286286
Some async stuff is more asynchronous than the other.
287287
@@ -307,7 +307,7 @@ Remember promise queue from the chapter <info:promise-queue>? Promise `.then/cat
307307
308308
`Async/await` is based on promises, so it uses the same promise queue internally.
309309
310-
So `await` is guaranteed to work before any `setTimeout` or other event handlers. That's actually quite essential, as we know that our async/await will never be interrupted by other handlers or events.
310+
So `await` is guaranteed to work before any `setTimeout` or other event handlers. That's actually quite essential, as we know that our async/await code flow will never be interrupted by other handlers or events.
311311
312312
## Summary
313313

0 commit comments

Comments
 (0)