4.7 לולאת האירועים ותכנות פונקציונלי תרגול
תרגול - לולאת האירועים ותכנות פונקציונלי¶
תרגיל 1 - סדר הפלט¶
בלי להריץ את הקוד, כתבו מה יהיה סדר הפלט בכל אחד מהמקרים הבאים. הסבירו למה.
א.
console.log("1");
setTimeout(() => {
console.log("2");
}, 0);
Promise.resolve().then(() => {
console.log("3");
});
queueMicrotask(() => {
console.log("4");
});
console.log("5");
ב.
async function first() {
console.log("A");
await second();
console.log("B");
}
async function second() {
console.log("C");
await Promise.resolve();
console.log("D");
}
console.log("E");
first();
console.log("F");
ג.
setTimeout(() => console.log("1"), 0);
Promise.resolve()
.then(() => {
console.log("2");
return Promise.resolve();
})
.then(() => {
console.log("3");
setTimeout(() => console.log("4"), 0);
});
Promise.resolve().then(() => {
console.log("5");
});
setTimeout(() => console.log("6"), 0);
console.log("7");
תרגיל 2 - חסימה ופתרון¶
הקוד הבא מחשב מספרים ראשוניים אבל חוסם את ה-UI:
function findPrimes(limit) {
const primes = [];
for (let i = 2; i <= limit; i++) {
let isPrime = true;
for (let j = 2; j <= Math.sqrt(i); j++) {
if (i % j === 0) {
isPrime = false;
break;
}
}
if (isPrime) {
primes.push(i);
}
}
return primes;
}
// this blocks the UI for seconds
const primes = findPrimes(5000000);
שכתבו את הפונקציה כך שהיא לא תחסום את ה-UI:
- פצלו את העבודה לחלקים (chunks) באמצעות setTimeout
- הוסיפו קולבק של התקדמות שמתעדכן כל חלק
- הוסיפו קולבק של סיום שמחזיר את התוצאה
findPrimesAsync(5000000, {
chunkSize: 10000,
onProgress(current, total) {
console.log(`${Math.round(current / total * 100)}%`);
},
onComplete(primes) {
console.log(`Found ${primes.length} primes`);
}
});
תרגיל 3 - פונקציות טהורות¶
בכל אחד מהמקרים הבאים, קבעו אם הפונקציה טהורה או לא, והסבירו למה. אם היא לא טהורה, שכתבו אותה כך שתהיה טהורה.
// a
function discount(price) {
return price * 0.9;
}
// b
let count = 0;
function increment() {
return ++count;
}
// c
function getFullName(user) {
return `${user.firstName} ${user.lastName}`;
}
// d
function addItem(cart, item) {
cart.push(item);
return cart;
}
// e
function getRandomGreeting(name) {
const greetings = ["Hello", "Hi", "Hey"];
const index = Math.floor(Math.random() * greetings.length);
return `${greetings[index]}, ${name}!`;
}
// f
function formatDate(date) {
return new Date(date).toLocaleDateString("he-IL");
}
תרגיל 4 - אי-שינוי¶
כתבו את הפונקציות הבאות בגישת אי-שינוי (immutable). אסור לשנות את האובייקטים המקוריים.
א. toggleTodo - מקבלת מערך של todos ו-id, ומחזירה מערך חדש שבו ה-todo המתאים הפוך (completed הופך מ-true ל-false ולהפך).
const todos = [
{ id: 1, text: "Learn JS", completed: false },
{ id: 2, text: "Learn React", completed: false },
{ id: 3, text: "Build project", completed: true }
];
const result = toggleTodo(todos, 2);
// result[1].completed === true
// todos[1].completed === false (original unchanged!)
ב. updateNestedProperty - מקבלת אובייקט מקונן, מערך של מפתחות (path), וערך חדש. מחזירה אובייקט חדש עם הערך המעודכן.
const state = {
user: {
name: "Alice",
address: {
city: "Tel Aviv",
street: "Rothschild"
}
},
settings: {
theme: "dark"
}
};
const newState = updateNestedProperty(state, ["user", "address", "city"], "Jerusalem");
// newState.user.address.city === "Jerusalem"
// state.user.address.city === "Tel Aviv" (original unchanged!)
// newState.settings === state.settings (same reference - not copied unnecessarily)
ג. groupBy - מקבלת מערך ומפתח, ומחזירה אובייקט שבו הפריטים מקובצים לפי הערך של המפתח.
const people = [
{ name: "Alice", department: "Engineering" },
{ name: "Bob", department: "Marketing" },
{ name: "Charlie", department: "Engineering" },
{ name: "Dana", department: "Marketing" },
{ name: "Eve", department: "Sales" }
];
const grouped = groupBy(people, "department");
// {
// Engineering: [{ name: "Alice", ... }, { name: "Charlie", ... }],
// Marketing: [{ name: "Bob", ... }, { name: "Dana", ... }],
// Sales: [{ name: "Eve", ... }]
// }
תרגיל 5 - הרכבה ו-pipe¶
א. כתבו פונקציית pipe שמקבלת כמות בלתי מוגבלת של פונקציות ומחזירה פונקציה חדשה שמפעילה אותן בסדר (משמאל לימין).
ב. השתמשו ב-pipe כדי לבנות את הפונקציות הבאות:
processUsername- מקבלת מחרוזת, חותכת רווחים, הופכת לאותיות קטנות, מחליפה רווחים ב-underscore, ומסירה תווים שאינם אותיות, ספרות או underscore.calculatePrice- מקבלת מחיר, מחיל הנחה של 10%, מוסיף מע"מ של 17%, ומעגל לשתי ספרות אחרי הנקודה.
ג. כתבו פונקציית pipeAsync שעובדת כמו pipe אבל תומכת בפונקציות אסינכרוניות (שמחזירות Promise).
const processUser = pipeAsync(
async (id) => {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
return response.json();
},
(user) => ({ ...user, name: user.name.toUpperCase() }),
(user) => `User: ${user.name} (${user.email})`
);
const result = await processUser(1);
// "User: LEANNE GRAHAM (Sincere@april.biz)"
תרגיל 6 - קרינג - Currying¶
א. כתבו פונקציית curry כללית שמקבלת פונקציה ומחזירה גרסה curried שלה.
function add(a, b, c) {
return a + b + c;
}
const curriedAdd = curry(add);
curriedAdd(1)(2)(3); // 6
curriedAdd(1, 2)(3); // 6
curriedAdd(1)(2, 3); // 6
curriedAdd(1, 2, 3); // 6
ב. השתמשו ב-currying כדי לבנות מערכת validators:
// curried validator builders
const minLength = curry((min, fieldName, value) => {
// returns null if valid, error message if invalid
});
const maxLength = curry((max, fieldName, value) => {
// ...
});
const matches = curry((regex, message, fieldName, value) => {
// ...
});
const required = curry((fieldName, value) => {
// ...
});
// compose validators for a field
const validateUsername = [
required("username"),
minLength(3, "username"),
maxLength(20, "username"),
matches(/^[a-zA-Z0-9_]+$/, "only letters, numbers, and underscores", "username")
];
const validateEmail = [
required("email"),
matches(/^[^\s@]+@[^\s@]+\.[^\s@]+$/, "invalid email format", "email")
];
// validate function that runs all validators
function validate(validators, value) {
// returns array of error messages (empty if valid)
}
validate(validateUsername, ""); // ["username is required"]
validate(validateUsername, "ab"); // ["username must be at least 3 characters"]
validate(validateUsername, "hello world");// ["username: only letters, numbers, and underscores"]
validate(validateUsername, "alice_123"); // []
validate(validateEmail, "not-an-email"); // ["email: invalid email format"]
תרגיל 7 - מיקרו-משימות בפועל¶
כתבו פונקציה scheduler שמנהלת תור משימות עם עדיפויות:
scheduler.addTask(fn, priority)- מוסיפה משימה עם עדיפות ("high", "normal", "low")- משימות high רצות כמיקרו-משימות (queueMicrotask)
- משימות normal רצות עם setTimeout(fn, 0)
- משימות low רצות עם requestAnimationFrame (אם זמין) או setTimeout(fn, 100)
scheduler.run()- מריצה את כל המשימות בתור
const scheduler = createScheduler();
scheduler.addTask(() => console.log("Normal 1"), "normal");
scheduler.addTask(() => console.log("High 1"), "high");
scheduler.addTask(() => console.log("Low 1"), "low");
scheduler.addTask(() => console.log("High 2"), "high");
scheduler.addTask(() => console.log("Normal 2"), "normal");
scheduler.run();
// expected order:
// High 1
// High 2
// Normal 1
// Normal 2
// Low 1