4.7 לולאת האירועים ותכנות פונקציונלי פתרון
פתרון - לולאת האירועים ותכנות פונקציונלי¶
פתרון תרגיל 1¶
א.
1ו-5- קוד סינכרוני, רץ מיד3- מיקרו-משימה (Promise.then)4- מיקרו-משימה (queueMicrotask)2- מאקרו-משימה (setTimeout)
מיקרו-משימות רצות לפי סדר ההרשמה (3 לפני 4), ושתיהן לפני המאקרו-משימה.
ב.
E- קוד סינכרוניA- ה-async function רצה סינכרונית עד ה-await הראשוןC- second רצה סינכרונית עד ה-await שלהF- חזרנו לקוד הסינכרוני אחרי הקריאה ל-firstD- המשך second אחרי ה-await (מיקרו-משימה)B- המשך first אחרי ה-await (מיקרו-משימה, אבל חיכתה ש-second תסתיים)
ג.
7- קוד סינכרוני2ו-5- מיקרו-משימות (Promise.then), לפי סדר הרשמה3- מיקרו-משימה שנוצרה מתוך ה-then של2(אחרי return Promise.resolve), רצה עדיין לפני מאקרו-משימות1ו-6- מאקרו-משימות (setTimeout), לפי סדר הרשמה4- מאקרו-משימה שנוצרה מתוך ה-then של3
פתרון תרגיל 2¶
function findPrimesAsync({ limit, chunkSize = 10000, onProgress, onComplete }) {
const primes = [];
let current = 2;
function processChunk() {
const end = Math.min(current + chunkSize, limit + 1);
for (let i = current; i < end; 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);
}
}
current = end;
if (onProgress) {
onProgress(current, limit);
}
if (current <= limit) {
setTimeout(processChunk, 0);
} else {
if (onComplete) {
onComplete(primes);
}
}
}
processChunk();
}
// usage
findPrimesAsync({
limit: 5000000,
chunkSize: 10000,
onProgress(current, total) {
console.log(`${Math.round(current / total * 100)}%`);
},
onComplete(primes) {
console.log(`Found ${primes.length} primes`);
}
});
פתרון תרגיל 3¶
// a - PURE: same input always gives same output, no side effects
function discount(price) {
return price * 0.9;
}
// b - NOT PURE: modifies external variable `count`
// fixed:
function increment(count) {
return count + 1;
}
// c - PURE: depends only on its input, returns a new string
function getFullName(user) {
return `${user.firstName} ${user.lastName}`;
}
// d - NOT PURE: mutates the input array
// fixed:
function addItem(cart, item) {
return [...cart, item];
}
// e - NOT PURE: Math.random() makes output unpredictable
// fixed (pass greeting as parameter):
function getGreeting(greeting, name) {
return `${greeting}, ${name}!`;
}
// f - PURE: same date string always produces the same output
// (assuming the locale behavior is consistent)
function formatDate(date) {
return new Date(date).toLocaleDateString("he-IL");
}
פתרון תרגיל 4¶
א. toggleTodo:
function toggleTodo(todos, id) {
return todos.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
);
}
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);
console.log(result[1].completed); // true
console.log(todos[1].completed); // false (unchanged)
ב. updateNestedProperty:
function updateNestedProperty(obj, path, value) {
if (path.length === 0) {
return value;
}
const [first, ...rest] = path;
return {
...obj,
[first]: rest.length === 0
? value
: updateNestedProperty(obj[first], rest, value)
};
}
const state = {
user: {
name: "Alice",
address: {
city: "Tel Aviv",
street: "Rothschild"
}
},
settings: {
theme: "dark"
}
};
const newState = updateNestedProperty(state, ["user", "address", "city"], "Jerusalem");
console.log(newState.user.address.city); // "Jerusalem"
console.log(state.user.address.city); // "Tel Aviv"
console.log(newState.settings === state.settings); // true (same reference)
ג. groupBy:
function groupBy(array, key) {
return array.reduce((groups, item) => {
const groupKey = item[key];
return {
...groups,
[groupKey]: [...(groups[groupKey] || []), item]
};
}, {});
}
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");
console.log(grouped);
// {
// Engineering: [{ name: "Alice", ... }, { name: "Charlie", ... }],
// Marketing: [{ name: "Bob", ... }, { name: "Dana", ... }],
// Sales: [{ name: "Eve", ... }]
// }
פתרון תרגיל 5¶
א. pipe:
function pipe(...fns) {
return function (value) {
return fns.reduce((acc, fn) => fn(acc), value);
};
}
ב. processUsername ו-calculatePrice:
const trim = str => str.trim();
const toLower = str => str.toLowerCase();
const replaceSpaces = str => str.replace(/\s+/g, "_");
const removeInvalid = str => str.replace(/[^a-z0-9_]/g, "");
const processUsername = pipe(trim, toLower, replaceSpaces, removeInvalid);
processUsername(" Hello World 123! "); // "hello_world_123"
const applyDiscount = price => price * 0.9;
const addVat = price => price * 1.17;
const roundToTwo = num => Math.round(num * 100) / 100;
const calculatePrice = pipe(applyDiscount, addVat, roundToTwo);
calculatePrice(100); // 105.3
ג. pipeAsync:
function pipeAsync(...fns) {
return function (value) {
return fns.reduce(
(promise, fn) => promise.then(fn),
Promise.resolve(value)
);
};
}
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);
console.log(result); // "User: LEANNE GRAHAM (Sincere@april.biz)"
פתרון תרגיל 6¶
א. curry:
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function (...moreArgs) {
return curried.apply(this, [...args, ...moreArgs]);
};
};
}
function add(a, b, c) {
return a + b + c;
}
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6
console.log(curriedAdd(1, 2, 3)); // 6
ב. מערכת validators:
const minLength = curry((min, fieldName, value) => {
if (value.length < min) {
return `${fieldName} must be at least ${min} characters`;
}
return null;
});
const maxLength = curry((max, fieldName, value) => {
if (value.length > max) {
return `${fieldName} must be at most ${max} characters`;
}
return null;
});
const matches = curry((regex, message, fieldName, value) => {
if (!regex.test(value)) {
return `${fieldName}: ${message}`;
}
return null;
});
const required = curry((fieldName, value) => {
if (!value || value.trim() === "") {
return `${fieldName} is required`;
}
return null;
});
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")
];
function validate(validators, value) {
return validators
.map(validator => validator(value))
.filter(error => error !== null);
}
console.log(validate(validateUsername, "")); // ["username is required"]
console.log(validate(validateUsername, "ab")); // ["username must be at least 3 characters"]
console.log(validate(validateUsername, "hello world")); // ["username: only letters, numbers, and underscores"]
console.log(validate(validateUsername, "alice_123")); // []
console.log(validate(validateEmail, "not-an-email")); // ["email: invalid email format"]
פתרון תרגיל 7¶
function createScheduler() {
const tasks = {
high: [],
normal: [],
low: []
};
return {
addTask(fn, priority = "normal") {
if (!tasks[priority]) {
throw new Error(`Invalid priority: ${priority}`);
}
tasks[priority].push(fn);
},
run() {
// high priority - microtasks
tasks.high.forEach(fn => {
queueMicrotask(fn);
});
// normal priority - macrotasks
tasks.normal.forEach(fn => {
setTimeout(fn, 0);
});
// low priority - requestAnimationFrame or delayed setTimeout
tasks.low.forEach(fn => {
if (typeof requestAnimationFrame !== "undefined") {
requestAnimationFrame(fn);
} else {
setTimeout(fn, 100);
}
});
// clear all queues
tasks.high = [];
tasks.normal = [];
tasks.low = [];
}
};
}
// testing
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();
// output:
// High 1
// High 2
// Normal 1
// Normal 2
// Low 1