|
| 1 | +## Writing a custom promisify function |
| 2 | + |
| 3 | +> Before writing our own custom promisify function, a couple of points on why we need to convert callbacks to promises. |
| 4 | +
|
| 5 | +1. In Callbacks, if we want to do something specific, we need to attach `err` argument to each callback which is redundant, whereas in promises or async/await, we can add a `.catch` block which will catch all the errors in the promise chain |
| 6 | +2. In Callbacks, we have no control over how many times it is being called, which can lead to memory leaks, when its called and under what context. |
| 7 | +3. Using Promises, we can control these factors, especially error handling, and the code is more readable and maintainable. |
| 8 | + |
| 9 | +### How to make a callback based function return a promise |
| 10 | +> There are 2 ways to do this: |
| 11 | +1. Wrap the function into another function which returns a promise and resolves or rejects based on the arguments. |
| 12 | +2. Promisification - we create or use a utility helper function `promisify` which will transform all error first callback APIs |
| 13 | + |
| 14 | +#### Callback based function |
| 15 | +```js |
| 16 | +const getSumAsync = (num1, num2, callback) => { |
| 17 | + if (!num1 || !num2) { |
| 18 | + return callback(new Error("Missing arguments"), null); |
| 19 | + } |
| 20 | + |
| 21 | + return callback(null, num1 + num2); |
| 22 | +} |
| 23 | + |
| 24 | +// Function call with callback |
| 25 | +getSumAsync(2, 3, (err, result) => { |
| 26 | + if (err) { |
| 27 | + doSomethingWithError(err); |
| 28 | + } else { |
| 29 | + console.log(result); |
| 30 | + } |
| 31 | +}); |
| 32 | +``` |
| 33 | + |
| 34 | +#### Wrap into a promise |
| 35 | +```js |
| 36 | +const getSumPromise = (num1, num2) => { |
| 37 | + return new Promise((resolve, reject) => { |
| 38 | + getSumAsync(num1, num2, (err, result) => { |
| 39 | + if (err) { |
| 40 | + reject(err); |
| 41 | + } else { |
| 42 | + resolve(result); |
| 43 | + } |
| 44 | + }); |
| 45 | + }); |
| 46 | +}; |
| 47 | + |
| 48 | +// Function call with Promisified function |
| 49 | +getSumPromise(2, 3) |
| 50 | + .then(result => console.log(result)) |
| 51 | + .catch(err => doSomethingWithError(err)); |
| 52 | +``` |
| 53 | + |
| 54 | +#### Promisify |
| 55 | +> Node.js has a util `promisify` function which converts a function that accepts a callback into a function returning a promise. |
| 56 | +
|
| 57 | +```js |
| 58 | +const { promisify } = require('util'); |
| 59 | +const getSumPromise = promisify(getSumAsync); |
| 60 | +getSumPromise(2, 3) |
| 61 | + .then(result => console.log(result)) |
| 62 | + .catch(err => doSomethingWithError(err)); |
| 63 | +``` |
| 64 | + |
| 65 | +### Writing our own promisify function |
| 66 | + |
| 67 | +* Step 1 |
| 68 | +> Our custom promise function takes the function to promisified as an argument |
| 69 | +
|
| 70 | +```js |
| 71 | +// Function should be called like below: |
| 72 | +const getSumPromise = customPromisify(getSumAsync); |
| 73 | + |
| 74 | +// So our custom promise function takes the function to promisified as an argument |
| 75 | +const customPromisify = (functionToBePromisified) => {}; |
| 76 | +``` |
| 77 | + |
| 78 | +* Step 2 |
| 79 | +> The custom promisify function returns a function back, which gets assigned to getSumPromise, which is eventually called with the same arguments of the original callback based function |
| 80 | +
|
| 81 | +```js |
| 82 | +const customPromisify = (functionToBePromisified) => { |
| 83 | + return (...args) => { |
| 84 | + |
| 85 | + } |
| 86 | +}; |
| 87 | +``` |
| 88 | + |
| 89 | +* Step 3 |
| 90 | +> When we call the getSumPromise function, the above returned function gets called. And in the implementation above, it returns a promise which we can use with getSumPromise |
| 91 | +
|
| 92 | +```js |
| 93 | +const customPromisify = (functionToBePromisified) => { |
| 94 | + return (...args) => { |
| 95 | + return new Promise((resolve, reject) => { |
| 96 | + |
| 97 | + }); |
| 98 | + } |
| 99 | +}; |
| 100 | +``` |
| 101 | + |
| 102 | +* Step 4 |
| 103 | +> The decision on when to resolve or reject the promise, will be made by the original callback based `getSumAsync` function and we just need to define the callback. Based on `err` and `result`, we will reject and resolve the promise. |
| 104 | +
|
| 105 | +```js |
| 106 | +const customPromisify = (functionToBePromisified) => { |
| 107 | + return (...args) => { |
| 108 | + return new Promise((resolve, reject) => { |
| 109 | + function customCallback(err, result) { |
| 110 | + if (err) { |
| 111 | + reject(err); |
| 112 | + } else { |
| 113 | + resolve(result); |
| 114 | + } |
| 115 | + } |
| 116 | + }) |
| 117 | + } |
| 118 | +} |
| 119 | +``` |
| 120 | + |
| 121 | +* Step 5 |
| 122 | +> Our `args` consists of only the arguments `(num1, num2)` as passed to getSumPromise function. It needs the callback function as the third argument which getSumAsync will call when it has processed the result. |
| 123 | +
|
| 124 | +> Finally we will call our function with `.call` (explicit binding) in order to call the `getSumAsync` function in the same context, and then our promisify function will resolve or reject accordingly. |
| 125 | +
|
| 126 | +```js |
| 127 | +const customPromisify = (functionToBePromisified) => { |
| 128 | + return (...args) => { |
| 129 | + return new Promise((resolve, reject) => { |
| 130 | + function customCallback(err, result) { |
| 131 | + if (err) { |
| 132 | + reject(err); |
| 133 | + } else { |
| 134 | + resolve(result); |
| 135 | + } |
| 136 | + } |
| 137 | + args.push(customCallback); |
| 138 | + functionToBePromisified.call(this, ...args); |
| 139 | + }); |
| 140 | + } |
| 141 | +}; |
| 142 | +``` |
| 143 | + |
| 144 | +> In case we have a function with callback which has multiple arguments (or results), we can modify our custom promise function as below: |
| 145 | +
|
| 146 | +```js |
| 147 | +const customPromisify = (functionToBePromisified) => { |
| 148 | + return (...args) => { |
| 149 | + return new Promise((resolve, reject) => { |
| 150 | + function customCallback(err, ...results) { |
| 151 | + if (err) { |
| 152 | + reject(err); |
| 153 | + } else { |
| 154 | + resolve(results.length === 1 ? results[0] : results); |
| 155 | + } |
| 156 | + } |
| 157 | + args.push(customCallback); |
| 158 | + functionToBePromisified.call(this, ...args); |
| 159 | + }); |
| 160 | + } |
| 161 | +}; |
| 162 | + |
| 163 | +// Example usage: |
| 164 | +const getSumAsync = (num1, num2, callback) => { |
| 165 | + if (!num1 || !num2) { |
| 166 | + callback(new Error("Missing arguments"), null); |
| 167 | + } |
| 168 | + |
| 169 | + const sum = num1 + num2; |
| 170 | + const msg = `The sum is ${sum}`; |
| 171 | + |
| 172 | + return callback(null, sum, msg); |
| 173 | +} |
| 174 | + |
| 175 | +const getSumPromise = customPromisify(getSumAsync); |
| 176 | +getSumPromise(2, 3).then(arrOfResults).catch(err) // arrOfResults is [5, "The sum is 5"] |
| 177 | +``` |
| 178 | + |
0 commit comments