Skip to content

Commit e62e678

Browse files
Promisifying a function
1 parent bb63f56 commit e62e678

File tree

1 file changed

+178
-0
lines changed

1 file changed

+178
-0
lines changed

concepts/Promisify.md

+178
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
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

Comments
 (0)