diff --git a/1-js/11-async/06-promisify/article.md b/1-js/11-async/06-promisify/article.md index 4ef62254..8c33dc77 100644 --- a/1-js/11-async/06-promisify/article.md +++ b/1-js/11-async/06-promisify/article.md @@ -4,6 +4,8 @@ Such transformations are often required in real-life, as many functions and libraries are callback-based. But promises are more convenient, so it makes sense to promisify them. +For better understanding, let's see an example. + For instance, we have `loadScript(src, callback)` from the chapter . ```js run @@ -21,8 +23,15 @@ function loadScript(src, callback) { // loadScript('path/script.js', (err, script) => {...}) ``` -Let's promisify it. The new `loadScriptPromise(src)` function achieves the same result, but it accepts only `src` (no `callback`) and returns a promise. +The function loads a script with the given `src`, and then calls `callback(err)` in case of an error, or `callback(null, script)` in case of successful loading. That's a widespread agreement for using callbacks, we saw it before. +Let's promisify it. + +We'll make a new function `loadScriptPromise(src)`, that does the same (loads the script), but returns a promise instead of using callbacks. + +In other words, we pass it only `src` (no `callback`) and get a promise in return, that resolves with `script` when the load is successful, and rejects with the error otherwise. + +Here it is: ```js let loadScriptPromise = function(src) { return new Promise((resolve, reject) => { @@ -37,19 +46,19 @@ let loadScriptPromise = function(src) { // loadScriptPromise('path/script.js').then(...) ``` -Now `loadScriptPromise` fits well in promise-based code. +As we can see, the new function is a wrapper around the original `loadScript` function. It calls it providing its own callback that translates to promise `resolve/reject`. -As we can see, it delegates all the work to the original `loadScript`, providing its own callback that translates to promise `resolve/reject`. +Now `loadScriptPromise` fits well in promise-based code. If we like promises more than callbacks (and soon we'll see more reasons for that), then we will use it instead. -In practice we'll probably need to promisify many functions, so it makes sense to use a helper. We'll call it `promisify(f)`: it accepts a to-promisify function `f` and returns a wrapper function. +In practice we may need to promisify more than one function, so it makes sense to use a helper. -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: +We'll call it `promisify(f)`: it accepts a to-promisify function `f` and returns a wrapper function. ```js function promisify(f) { - return function (...args) { // return a wrapper-function + return function (...args) { // return a wrapper-function (*) return new Promise((resolve, reject) => { - function callback(err, result) { // our custom callback for f + function callback(err, result) { // our custom callback for f (**) if (err) { reject(err); } else { @@ -69,11 +78,18 @@ let loadScriptPromise = promisify(loadScript); loadScriptPromise(...).then(...); ``` -Here we assume that the original function expects a callback with two arguments `(err, result)`. That's what we encounter most often. Then our custom callback is in exactly the right format, and `promisify` works great for such a case. +The code may look a bit complex, but it's essentially the same that we wrote above, while promisifying `loadScript` function. + +A call to `promisify(f)` returns a wrapper around `f` `(*)`. That wrapper returns a promise and forwards the call to the original `f`, tracking the result in the custom callback `(**)`. + +Here, `promisiefy` assumes that the original function expects a callback with exactly two arguments `(err, result)`. That's what we encounter most often. Then our custom callback is in exactly the right format, and `promisify` works great for such a case. But what if the original `f` expects a callback with more arguments `callback(err, res1, res2, ...)`? -Here's a more advanced version of `promisify`: if called as `promisify(f, true)`, the promise result will be an array of callback results `[res1, res2, ...]`: +We can improve our helper. Let's make a more advanced version of `promisify`. + +- When called as `promisify(f)` it should work similar to the version above. +- When called as `promisify(f, true)`, it should return the promise that resolves with the array of callback results. That's exactly for callbacks with many arguments. ```js // promisify(f, true) to get array of results @@ -101,6 +117,8 @@ f = promisify(f, true); f(...).then(arrayOfResults => ..., err => ...) ``` +As you can see it's essentially the same as above, but `resolve` is called with only one or all arguments depending on whether `manyArgs` is truthy. + For more exotic callback formats, like those without `err` at all: `callback(result)`, we can promisify such functions manually without using the helper. 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.