4.5 פרומיסים ו async await פתרון
פתרון - פרומיסים ו-async/await¶
תרגיל 1 - פרומיסים בסיסיים¶
חלק א:¶
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// test
async function demo() {
console.log("Start");
await delay(2000);
console.log("2 seconds later");
}
demo();
חלק ב:¶
function randomSuccess(chance) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < chance) {
resolve("Operation succeeded");
} else {
reject(new Error("Operation failed"));
}
}, 1000);
});
}
// test
randomSuccess(0.7)
.then((msg) => console.log(msg))
.catch((err) => console.log(err.message));
תרגיל 2 - שרשור Promises¶
function fetchUser(id) {
return new Promise(resolve => {
setTimeout(() => {
resolve({ id, name: "Alice", departmentId: 10 });
}, 500);
});
}
function fetchDepartment(id) {
return new Promise(resolve => {
setTimeout(() => {
resolve({ id, name: "Engineering", managerId: 42 });
}, 500);
});
}
function fetchManager(id) {
return new Promise(resolve => {
setTimeout(() => {
resolve({ id, name: "Bob", email: "bob@company.com" });
}, 500);
});
}
גרסה עם שרשור then:
function getUserManagerEmail(userId) {
return fetchUser(userId)
.then(user => fetchDepartment(user.departmentId))
.then(dept => fetchManager(dept.managerId))
.then(manager => manager.email);
}
getUserManagerEmail(1).then(email => console.log(email));
// "bob@company.com" (after ~1.5 seconds)
גרסה עם async/await:
async function getUserManagerEmail(userId) {
const user = await fetchUser(userId);
const department = await fetchDepartment(user.departmentId);
const manager = await fetchManager(department.managerId);
return manager.email;
}
const email = await getUserManagerEmail(1);
console.log(email); // "bob@company.com"
תרגיל 3 - Promise.all לעומת ריצה סדרתית¶
function fetchUsers() {
return new Promise(resolve =>
setTimeout(() => resolve(["Alice", "Bob"]), 1000)
);
}
function fetchPosts() {
return new Promise(resolve =>
setTimeout(() => resolve(["Post 1", "Post 2"]), 1000)
);
}
function fetchComments() {
return new Promise(resolve =>
setTimeout(() => resolve(["Comment 1"]), 1000)
);
}
חלק א - סדרתי:¶
async function loadAllSequential() {
const users = await fetchUsers();
const posts = await fetchPosts();
const comments = await fetchComments();
return { users, posts, comments };
}
console.time("sequential");
const data = await loadAllSequential();
console.timeEnd("sequential"); // ~3000ms
console.log(data);
חלק ב - מקבילי:¶
async function loadAllParallel() {
const [users, posts, comments] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchComments()
]);
return { users, posts, comments };
}
console.time("parallel");
const data2 = await loadAllParallel();
console.timeEnd("parallel"); // ~1000ms
console.log(data2);
ההבדל: סדרתי לוקח ~3 שניות (1+1+1), מקבילי לוקח ~1 שניה (הכל רץ ביחד).
תרגיל 4 - טיפול בשגיאות¶
async function safeFetch(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const data = await response.json();
return { ok: true, data };
} catch (error) {
return { ok: false, error: error.message };
} finally {
console.log("Done");
}
}
// test
const result = await safeFetch("/api/users");
if (result.ok) {
console.log("Data:", result.data);
} else {
console.log("Error:", result.error);
}
async function fetchMultiple(urls) {
const results = await Promise.allSettled(
urls.map(url => safeFetch(url))
);
return results.map((result, i) => ({
url: urls[i],
...result.value // safeFetch always returns { ok, data/error }
}));
}
// test
const results = await fetchMultiple([
"/api/users",
"/api/nonexistent",
"/api/posts"
]);
results.forEach(r => {
if (r.ok) {
console.log(`${r.url}: success`);
} else {
console.log(`${r.url}: failed - ${r.error}`);
}
});
תרגיל 5 - retry עם backoff¶
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function retryAsync(fn, maxRetries = 3, initialDelay = 1000) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const result = await fn();
console.log(`Attempt ${attempt} succeeded!`);
return result;
} catch (error) {
lastError = error;
const delayMs = initialDelay * Math.pow(2, attempt - 1);
if (attempt < maxRetries) {
console.log(
`Attempt ${attempt} failed: ${error.message} (retrying in ${delayMs}ms)`
);
await delay(delayMs);
} else {
console.log(`Attempt ${attempt} failed: ${error.message} (no more retries)`);
}
}
}
throw lastError;
}
// test
let callCount = 0;
async function unreliableApi() {
callCount++;
if (callCount < 3) {
throw new Error("Server error");
}
return { data: "success" };
}
const result = await retryAsync(unreliableApi);
console.log(result); // { data: "success" }
// Output:
// Attempt 1 failed: Server error (retrying in 1000ms)
// Attempt 2 failed: Server error (retrying in 2000ms)
// Attempt 3 succeeded!
// { data: "success" }
תרגיל 6 - Promise.race - timeout¶
חלק א:¶
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) => {
setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms);
});
return Promise.race([promise, timeout]);
}
// test
const fast = delay(500).then(() => "fast result");
const slow = delay(5000).then(() => "slow result");
console.log(await withTimeout(fast, 1000)); // "fast result"
try {
await withTimeout(slow, 1000);
} catch (error) {
console.log(error.message); // "Timeout after 1000ms"
}
חלק ב:¶
async function fetchFastest(urls) {
const fetchPromises = urls.map(async (url) => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status} from ${url}`);
}
return response.json();
});
return Promise.any(fetchPromises);
}
// test
try {
const result = await fetchFastest([
"https://slow-api.com/data",
"https://fast-api.com/data",
"https://medium-api.com/data"
]);
console.log("Fastest result:", result);
} catch (error) {
// AggregateError - all failed
console.log("All requests failed:", error.errors);
}
Promise.any מחזיר את התוצאה של ה-Promise הראשון שמצליח. אם כולם נכשלים, זורק AggregateError עם כל השגיאות.
תרגיל 7 - עיבוד תור משימות¶
class AsyncQueue {
#queue = [];
#processing = false;
async enqueue(asyncFn) {
return new Promise((resolve, reject) => {
this.#queue.push({ asyncFn, resolve, reject });
this.#processNext();
});
}
async #processNext() {
if (this.#processing || this.#queue.length === 0) {
return;
}
this.#processing = true;
const { asyncFn, resolve, reject } = this.#queue.shift();
try {
const result = await asyncFn();
resolve(result);
} catch (error) {
reject(error);
} finally {
this.#processing = false;
this.#processNext();
}
}
getQueueSize() {
return this.#queue.length;
}
isProcessing() {
return this.#processing;
}
}
// helper
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// test
const queue = new AsyncQueue();
queue.enqueue(async () => {
await delay(1000);
console.log("Task 1 done");
return "result 1";
});
queue.enqueue(async () => {
await delay(500);
console.log("Task 2 done");
return "result 2";
});
const result3 = await queue.enqueue(async () => {
await delay(200);
console.log("Task 3 done");
return "result 3";
});
console.log(result3); // "result 3"
// Output:
// "Task 1 done" (after 1s)
// "Task 2 done" (after 1.5s)
// "Task 3 done" (after 1.7s)
// "result 3"
איך זה עובד:
1. enqueue יוצרת Promise חדש ושומרת את resolve/reject שלו בתור
2. #processNext בודקת אם יש משימה בתור ואם לא רצה כבר משימה
3. כשמשימה מסתיימת, finally קורא ל-#processNext שוב כדי לעבד את הבאה
4. ה-Promise שהוחזר ל-caller מתקיים כשהמשימה שלו (לא הראשונה בתור) מסתיימת
בונוס - concurrency:¶
class AsyncQueue {
#queue = [];
#running = 0;
#concurrency;
constructor(concurrency = 1) {
this.#concurrency = concurrency;
}
async enqueue(asyncFn) {
return new Promise((resolve, reject) => {
this.#queue.push({ asyncFn, resolve, reject });
this.#processNext();
});
}
async #processNext() {
if (this.#running >= this.#concurrency || this.#queue.length === 0) {
return;
}
this.#running++;
const { asyncFn, resolve, reject } = this.#queue.shift();
try {
const result = await asyncFn();
resolve(result);
} catch (error) {
reject(error);
} finally {
this.#running--;
this.#processNext();
}
}
getQueueSize() {
return this.#queue.length;
}
isProcessing() {
return this.#running > 0;
}
}
// with concurrency of 2:
const queue = new AsyncQueue(2);
// tasks 1 and 2 run in parallel, task 3 waits for one to finish
עם concurrency, במקום לבדוק this.#processing, סופרים כמה משימות רצות (#running) ומשווים ל-#concurrency. זה מאפשר לרוץ כמה משימות במקביל, אבל לא יותר מהמגבלה.