Skip to content

Commit b8c2f2e

Browse files
committed
Add solutions for 5th chapter
1 parent 7775726 commit b8c2f2e

File tree

5 files changed

+254
-0
lines changed

5 files changed

+254
-0
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
Implement your own version of Promise.
3+
all() leveraging promises, async/await, or a combination of the two.
4+
The function must be functionally equivalent to its original counterpart.
5+
*/
6+
7+
// 1. Promise.all using async await
8+
async function promiseAll(promises) {
9+
try {
10+
for (const promise of promises) {
11+
await promise;
12+
}
13+
} catch (e) {
14+
console.log('Error in promiseAll', e);
15+
}
16+
}
17+
18+
// 2. Promise.all using promises
19+
async function promiseAll2(promises) {
20+
return new Promise((resolve, reject) => {
21+
22+
let resolvedPromises = 0;
23+
promises.forEach((promise) => {
24+
25+
promise.then(() => {
26+
resolvedPromises++;
27+
if (resolvedPromises === promises.length) resolve();
28+
}, reject);
29+
30+
})
31+
});
32+
}
33+
34+
35+
36+
// testing
37+
function wait(ms) {
38+
return new Promise((resolve, reject) => {
39+
setTimeout(() => {
40+
console.log(`Resolved ${ms}`);
41+
resolve('Resolved successfully');
42+
}, ms);
43+
});
44+
}
45+
46+
promiseAll2([wait(150), wait(399), wait(1500), wait(400), wait(5000), wait(5000)]).then(() => {
47+
console.log('ALL promises are resolved');
48+
})
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/* Migrate the TaskQueue class internals from
2+
promises to async/await where possible. Hint: you won't be able to use
3+
async/await everywhere.
4+
*/
5+
6+
export class TaskQueue {
7+
constructor(concurrency) {
8+
this.concurrency = concurrency
9+
this.running = 0
10+
this.queue = []
11+
}
12+
13+
// cannot be migrated to async because of error handling issues
14+
runTask(task) {
15+
return new Promise((resolve, reject) => {
16+
this.queue.push(() => {
17+
return task().then(resolve, reject)
18+
})
19+
20+
process.nextTick(this.next.bind(this))
21+
})
22+
}
23+
24+
async next() {
25+
while (this.running < this.concurrency && this.queue.length) {
26+
const task = this.queue.shift();
27+
this.running++
28+
try {
29+
await task();
30+
} finally {
31+
this.running--
32+
this.next()
33+
}
34+
}
35+
}
36+
}
37+
38+
const MAX_CONCURRENCY = 4;
39+
const queue = new TaskQueue(MAX_CONCURRENCY);
40+
41+
function wait(ms) {
42+
return new Promise((resolve, reject) => {
43+
setTimeout(() => {
44+
console.log(`Resolved ${ms}`);
45+
resolve('Resolved successfully');
46+
}, ms);
47+
});
48+
}
49+
50+
function taskCreator (ms) {
51+
return async function () {
52+
await wait(ms);
53+
}
54+
}
55+
56+
[taskCreator(3000), taskCreator(3000), taskCreator(3000), taskCreator(3000), taskCreator(3000)].forEach((task) => {
57+
queue.runTask(task);
58+
})
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
5.3 Producer-consumer with promises: Update the TaskQueuePC class
3+
internal methods so that they use just promises, removing any use of the
4+
async/await syntax. Hint: the infinite loop must become an asynchronous
5+
recursion. Beware of the recursive Promise resolution memory leak!
6+
*/
7+
8+
export class TaskQueuePC {
9+
constructor(concurrency) {
10+
this.taskQueue = [];
11+
this.consumerQueue = [];
12+
13+
// spawn consumers
14+
for (let i = 0; i < concurrency; i++) {
15+
this.consumer();
16+
}
17+
}
18+
19+
async consumer() {
20+
// ===== changed lines
21+
return new Promise((resolve, reject) => {
22+
const consumeTask = () => {
23+
this.getNextTask()
24+
.then(task => task())
25+
.then(() => consumeTask())
26+
.catch(error => reject(error));
27+
}
28+
29+
consumeTask();
30+
});
31+
// ======
32+
}
33+
34+
async getNextTask() {
35+
return new Promise((resolve) => {
36+
if (this.taskQueue.length !== 0) {
37+
return resolve(this.taskQueue.shift());
38+
}
39+
40+
this.consumerQueue.push(resolve);
41+
})
42+
}
43+
44+
async runTask(task) {
45+
return new Promise((resolve, reject) => {
46+
const taskWrapper = () => {
47+
const taskPromise = task();
48+
taskPromise.then(resolve, reject);
49+
return taskPromise;
50+
}
51+
52+
if (this.consumerQueue.length !== 0) {
53+
const consumer = this.consumerQueue.shift();
54+
consumer(taskWrapper);
55+
} else {
56+
this.taskQueue.push(taskWrapper);
57+
}
58+
})
59+
}
60+
61+
}
62+
63+
// === TESTING
64+
65+
const MAX_CONCURRENCY = 4;
66+
const queue = new TaskQueuePC(MAX_CONCURRENCY);
67+
68+
function wait(ms) {
69+
return new Promise((resolve, reject) => {
70+
setTimeout(() => {
71+
console.log(`Resolved ${ms}`);
72+
resolve('Resolved successfully');
73+
}, ms);
74+
});
75+
}
76+
77+
function taskCreator (ms) {
78+
return async function () {
79+
await wait(ms);
80+
}
81+
}
82+
83+
[taskCreator(3000), taskCreator(3000), taskCreator(3000), taskCreator(3000), taskCreator(3000)].forEach((task) => {
84+
queue.runTask(task);
85+
})
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
5.4 An asynchronous map() : Implement a parallel asynchronous version
3+
of Array.map() that supports promises and a concurrency limit. The
4+
function should not directly leverage the TaskQueue or TaskQueuePC
5+
classes we presented in this chapter, but it can use the underlying patterns.
6+
The function, which we will define as mapAsync(iterable, callback,
7+
concurrency) , will accept the following as inputs:
8+
• An iterable , such as an array.
9+
• A callback , which will receive as the input each item of the iterable
10+
(exactly like in the original Array.map() ) and can return either
11+
a Promise or a simple value.
12+
• A concurrency , which defines how many items in the iterable can
13+
be processed by callback in parallel at each given time.
14+
*/
15+
16+
async function mapAsync(iterable, callback, concurrency) {
17+
// trigger calls while running operations count is less than concurrency
18+
// when reached concurrency do nothing
19+
// when all operations resolved return
20+
21+
// after getting concurrency result decrement running operations
22+
// if (not reached) trigger calling operations
23+
24+
let id = 0;
25+
let runningOps = 0;
26+
let resolvedOps = 0;
27+
const resultArr = [];
28+
29+
return new Promise((resolve, reject) => {
30+
const runCallbacks = () => {
31+
while (runningOps < concurrency && id < iterable.length) {
32+
runningOps++;
33+
const currentOpId = id;
34+
callback(iterable[id]).then((result) => {
35+
resultArr[currentOpId] = result;
36+
runningOps--;
37+
resolvedOps++;
38+
runCallbacks();
39+
}).catch(reject);
40+
id++;
41+
}
42+
43+
if (resolvedOps === iterable.length) return resolve(resultArr);
44+
}
45+
46+
runCallbacks();
47+
});
48+
}
49+
50+
// TESTING
51+
function wait(ms) {
52+
return new Promise((resolve, reject) => {
53+
setTimeout(() => {
54+
console.log(`Resolved ${ms}`);
55+
resolve(`Resolved ${ms}`);
56+
}, ms);
57+
});
58+
}
59+
60+
61+
const MAX_CONCURRENCY = 2;
62+
63+
mapAsync([1000, 3000, 500, 1500, 2000], wait, MAX_CONCURRENCY).then((res) => console.log('RESOLVED ALL', res));

05-promises-async-patterns/f

Whitespace-only changes.

0 commit comments

Comments
 (0)