From 364e707b2a487da0cb775457a7ecabd960774167 Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Sat, 9 Mar 2019 17:24:12 +0300 Subject: [PATCH] promisify --- 1-js/11-async/07-promisify/article.md | 118 ++++++++++++++++++ .../01-rewrite-async/solution.md | 0 .../01-rewrite-async/task.md | 0 .../02-rewrite-async-2/solution.md | 0 .../02-rewrite-async-2/task.md | 0 .../03-async-from-regular/solution.md | 0 .../03-async-from-regular/task.md | 0 .../article.md | 4 +- .../head.html | 0 9 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 1-js/11-async/07-promisify/article.md rename 1-js/11-async/{07-async-await => 08-async-await}/01-rewrite-async/solution.md (100%) rename 1-js/11-async/{07-async-await => 08-async-await}/01-rewrite-async/task.md (100%) rename 1-js/11-async/{07-async-await => 08-async-await}/02-rewrite-async-2/solution.md (100%) rename 1-js/11-async/{07-async-await => 08-async-await}/02-rewrite-async-2/task.md (100%) rename 1-js/11-async/{07-async-await => 08-async-await}/03-async-from-regular/solution.md (100%) rename 1-js/11-async/{07-async-await => 08-async-await}/03-async-from-regular/task.md (100%) rename 1-js/11-async/{07-async-await => 08-async-await}/article.md (98%) rename 1-js/11-async/{07-async-await => 08-async-await}/head.html (100%) diff --git a/1-js/11-async/07-promisify/article.md b/1-js/11-async/07-promisify/article.md new file mode 100644 index 00000000..17e3dbd1 --- /dev/null +++ b/1-js/11-async/07-promisify/article.md @@ -0,0 +1,118 @@ +# Promisification + +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. + +In other words, we create a wrapper-function that does the same, internally calling the original one, but returns a promise. + +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. + +For instance, we have `loadScript(src, callback)` from the chapter . + +```js run +function loadScript(src, callback) { + let script = document.createElement('script'); + script.src = src; + + script.onload = () => callback(null, script); + script.onerror = () => callback(new Error(`Script load error for ${src}`)); + + document.head.append(script); +} + +// usage: +// loadScript('path/script.js', (err, script) => {...}) +``` + +Let's promisify it. The new `loadScriptPromise(src)` function will do the same, but accept only `src` (no callback) and return a promise. + +```js +let loadScriptPromise = function(src) { + return new Promise((resolve, reject) => { + loadScript(src, (err, script) => { + if (err) reject(err) + elsee resolve(script); + }); + }) +} + +// usage: +// loadScriptPromise('path/script.js').then(...) +``` + +Now `loadScriptPromise` fits well in our promise-based code. + +As we can see, it delegates all the work to the original `loadScript`, providing its own callback that translates to promise `resolve/reject`. + +As we may need to promisify many functions, it makes sense to use a helper. + +That's actually very simple -- `promisify(f)` below takes a to-promisify function `f` and returns a wrapper function. + +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: + +```js +function promisify(f) { + return function (...args) { // return a wrapper-function + return new Promise((resolve, reject) => { + function callback(err, result) { // our custom callback for f + if (err) { + return reject(err); + } else { + resolve(result); + } + } + + args.push(callback); // append our custom callback to the end of arguments + + f.call(this, ...args); // call the original function + }); + }; +}; + +// usage: +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 meet most often. Then our custom callbacks is exactly in the right format, and `promisify` works great for such case. + +But what is the original `f` expects a callback with more arguments `callback(err, res1, res2)`? + +Here's a modification of `promisify` that returns an array of multiple callback results: + +```js +// promisify(f, true) to get array of results +function promisify(f, manyArgs = false) { + return function (...args) { + return new Promise((resolve, reject) => { + function *!*callback(err, ...results*/!*) { // our custom callback for f + if (err) { + return reject(err); + } else { + // resolve with all callback results if manyArgs is specified + *!*resolve(manyArgs ? results : results[0]);*/!* + } + } + + args.push(callback); + + f.call(this, ...args); + }); + }; +}; + +// usage: +f = promisify(f, true); +f(...).then(err => ..., arrayOfResults => ...) +``` + +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. + +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. + +```smart +Promisification is a great approach, especially when you use `async/await` (see the next chapter), but not a total replacement for callbacks. + +Remember, a promise may have only one result, but a callback may technically be called many times. + +So promisification is only meant for functions that call the callback once. Furhter calls will be ignored. +``` diff --git a/1-js/11-async/07-async-await/01-rewrite-async/solution.md b/1-js/11-async/08-async-await/01-rewrite-async/solution.md similarity index 100% rename from 1-js/11-async/07-async-await/01-rewrite-async/solution.md rename to 1-js/11-async/08-async-await/01-rewrite-async/solution.md diff --git a/1-js/11-async/07-async-await/01-rewrite-async/task.md b/1-js/11-async/08-async-await/01-rewrite-async/task.md similarity index 100% rename from 1-js/11-async/07-async-await/01-rewrite-async/task.md rename to 1-js/11-async/08-async-await/01-rewrite-async/task.md diff --git a/1-js/11-async/07-async-await/02-rewrite-async-2/solution.md b/1-js/11-async/08-async-await/02-rewrite-async-2/solution.md similarity index 100% rename from 1-js/11-async/07-async-await/02-rewrite-async-2/solution.md rename to 1-js/11-async/08-async-await/02-rewrite-async-2/solution.md diff --git a/1-js/11-async/07-async-await/02-rewrite-async-2/task.md b/1-js/11-async/08-async-await/02-rewrite-async-2/task.md similarity index 100% rename from 1-js/11-async/07-async-await/02-rewrite-async-2/task.md rename to 1-js/11-async/08-async-await/02-rewrite-async-2/task.md diff --git a/1-js/11-async/07-async-await/03-async-from-regular/solution.md b/1-js/11-async/08-async-await/03-async-from-regular/solution.md similarity index 100% rename from 1-js/11-async/07-async-await/03-async-from-regular/solution.md rename to 1-js/11-async/08-async-await/03-async-from-regular/solution.md diff --git a/1-js/11-async/07-async-await/03-async-from-regular/task.md b/1-js/11-async/08-async-await/03-async-from-regular/task.md similarity index 100% rename from 1-js/11-async/07-async-await/03-async-from-regular/task.md rename to 1-js/11-async/08-async-await/03-async-from-regular/task.md diff --git a/1-js/11-async/07-async-await/article.md b/1-js/11-async/08-async-await/article.md similarity index 98% rename from 1-js/11-async/07-async-await/article.md rename to 1-js/11-async/08-async-await/article.md index b3ca9644..8a27b0f7 100644 --- a/1-js/11-async/07-async-await/article.md +++ b/1-js/11-async/08-async-await/article.md @@ -281,7 +281,7 @@ In case of an error, it propagates as usual: from the failed promise to `Promise ```` -## Async/await versus other async actions +## Timiing: async/await and higher-level actions Some async stuff is more asynchronous than the other. @@ -307,7 +307,7 @@ Remember promise queue from the chapter ? Promise `.then/cat `Async/await` is based on promises, so it uses the same promise queue internally. -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. +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. ## Summary diff --git a/1-js/11-async/07-async-await/head.html b/1-js/11-async/08-async-await/head.html similarity index 100% rename from 1-js/11-async/07-async-await/head.html rename to 1-js/11-async/08-async-await/head.html