JavaScript
Advanced JavaScript patterns, ES6+, asynchronous programming, dan modern JavaScript development
Pengenalan
JavaScript modern telah berkembang menjadi bahasa yang powerful dan expressive. courses ini akan mengajarkan Anda konsep-konsep advanced seperti closures, prototypes, async/await, functional programming, dan design patterns yang digunakan dalam production applications.
ES6+ Features
Destructuring
// Array destructuring
const numbers = [1, 2, 3, 4, 5];
const [first, second, ...rest] = numbers;
console.log(first); // 1
console.log(second); // 2
console.log(rest); // [3, 4, 5]
// Object destructuring
const user = {
name: 'John Doe',
email: 'john@example.com',
age: 30,
address: {
city: 'Jakarta',
country: 'Indonesia'
}
};
const { name, email, age: userAge } = user;
const { address: { city } } = user;
// Default values
const { role = 'user' } = user;
// Function parameters
function greet({ name, age = 18 }) {
console.log(`Hello ${name}, you are ${age} years old`);
}
greet({ name: 'Alice' }); // Hello Alice, you are 18 years oldSpread dan Rest Operators
// Spread operator
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]
// Clone array
const original = [1, 2, 3];
const clone = [...original];
// Clone object
const user = { name: 'John', age: 30 };
const userCopy = { ...user };
// Merge objects
const defaults = { theme: 'light', lang: 'en' };
const userPrefs = { theme: 'dark' };
const settings = { ...defaults, ...userPrefs }; // { theme: 'dark', lang: 'en' }
// Rest parameters
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15Template Literals
// Basic usage
const name = 'John';
const greeting = `Hello, ${name}!`;
// Multi-line strings
const html = `
<div class="card">
<h2>${name}</h2>
<p>Welcome to our site</p>
</div>
`;
// Expression evaluation
const price = 100;
const tax = 0.1;
const total = `Total: $${(price * (1 + tax)).toFixed(2)}`;
// Tagged templates
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
return `${result}${str}<mark>${values[i] || ''}</mark>`;
}, '');
}
const user = 'John';
const action = 'logged in';
const message = highlight`User ${user} has ${action}`;
// "User <mark>John</mark> has <mark>logged in</mark>"Arrow Functions
// Basic syntax
const add = (a, b) => a + b;
const square = x => x * x;
const greet = () => 'Hello!';
// Implicit return
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
// Explicit return
const complexOperation = (x, y) => {
const result = x * y;
return result + 10;
};
// Lexical this
class Counter {
constructor() {
this.count = 0;
}
increment() {
// Arrow function preserves 'this'
setTimeout(() => {
this.count++;
console.log(this.count);
}, 1000);
}
}
// When NOT to use arrow functions
const obj = {
name: 'John',
// ❌ Don't use arrow function for methods
greet: () => {
console.log(`Hello, ${this.name}`); // 'this' is undefined
},
// ✅ Use regular function
greet2() {
console.log(`Hello, ${this.name}`); // Works correctly
}
};Asynchronous JavaScript
Promises
// Creating a promise
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = { id: 1, name: 'John' };
resolve(data);
// or reject(new Error('Failed to fetch'));
}, 1000);
});
};
// Using promises
fetchData()
.then(data => {
console.log('Data:', data);
return data.id;
})
.then(id => {
console.log('ID:', id);
})
.catch(error => {
console.error('Error:', error);
})
.finally(() => {
console.log('Cleanup');
});
// Promise.all - Wait for all promises
const promise1 = fetch('/api/users');
const promise2 = fetch('/api/posts');
const promise3 = fetch('/api/comments');
Promise.all([promise1, promise2, promise3])
.then(([users, posts, comments]) => {
console.log('All data loaded');
})
.catch(error => {
console.error('One of the requests failed:', error);
});
// Promise.race - First to complete
Promise.race([
fetch('/api/fast'),
fetch('/api/slow')
])
.then(result => {
console.log('First result:', result);
});
// Promise.allSettled - Wait for all, regardless of success/failure
Promise.allSettled([promise1, promise2, promise3])
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('Success:', result.value);
} else {
console.log('Failed:', result.reason);
}
});
});Async/Await
// Basic async/await
async function fetchUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
return user;
} catch (error) {
console.error('Error fetching user:', error);
throw error;
}
}
// Sequential execution
async function processUsers() {
const user1 = await fetchUser(1);
const user2 = await fetchUser(2);
const user3 = await fetchUser(3);
return [user1, user2, user3];
}
// Parallel execution
async function processUsersParallel() {
const [user1, user2, user3] = await Promise.all([
fetchUser(1),
fetchUser(2),
fetchUser(3)
]);
return [user1, user2, user3];
}
// Error handling
async function safeOperation() {
try {
const data = await riskyOperation();
return { success: true, data };
} catch (error) {
return { success: false, error: error.message };
}
}
// Async iteration
async function* generateNumbers() {
for (let i = 0; i < 5; i++) {
await new Promise(resolve => setTimeout(resolve, 1000));
yield i;
}
}
async function consumeNumbers() {
for await (const num of generateNumbers()) {
console.log(num); // 0, 1, 2, 3, 4 (with 1s delay)
}
}Fetch API
// GET request
async function getUsers() {
try {
const response = await fetch('/api/users');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const users = await response.json();
return users;
} catch (error) {
console.error('Fetch error:', error);
throw error;
}
}
// POST request
async function createUser(userData) {
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(userData)
});
return response.json();
}
// PUT request
async function updateUser(id, userData) {
const response = await fetch(`/api/users/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(userData)
});
return response.json();
}
// DELETE request
async function deleteUser(id) {
const response = await fetch(`/api/users/${id}`, {
method: 'DELETE'
});
return response.ok;
}
// Upload file
async function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
return response.json();
}
// Abort request
const controller = new AbortController();
fetch('/api/data', {
signal: controller.signal
})
.then(response => response.json())
.catch(error => {
if (error.name === 'AbortError') {
console.log('Request aborted');
}
});
// Abort after 5 seconds
setTimeout(() => controller.abort(), 5000);Advanced Concepts
Closures
// Basic closure
function createCounter() {
let count = 0;
return {
increment() {
count++;
return count;
},
decrement() {
count--;
return count;
},
getCount() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2
// Private variables
function createBankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit(amount) {
if (amount > 0) {
balance += amount;
return balance;
}
throw new Error('Amount must be positive');
},
withdraw(amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
return balance;
}
throw new Error('Invalid amount');
},
getBalance() {
return balance;
}
};
}
const account = createBankAccount(1000);
account.deposit(500); // 1500
account.withdraw(200); // 1300
// balance is private, cannot be accessed directly
// Function factory
function createMultiplier(multiplier) {
return function(number) {
return number * multiplier;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15Prototypes dan Inheritance
// Constructor function
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
return `Hello, I'm ${this.name}`;
};
Person.prototype.getAge = function() {
return this.age;
};
const john = new Person('John', 30);
console.log(john.greet()); // "Hello, I'm John"
// Inheritance
function Employee(name, age, position) {
Person.call(this, name, age);
this.position = position;
}
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
Employee.prototype.getPosition = function() {
return this.position;
};
const employee = new Employee('Alice', 25, 'Developer');
console.log(employee.greet()); // "Hello, I'm Alice"
console.log(employee.getPosition()); // "Developer"
// ES6 Classes (syntactic sugar)
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
speak() {
return `${this.name} barks`;
}
getBreed() {
return this.breed;
}
}
const dog = new Dog('Buddy', 'Golden Retriever');
console.log(dog.speak()); // "Buddy barks"This Keyword
// Global context
console.log(this); // window (browser) or global (Node.js)
// Object method
const obj = {
name: 'John',
greet() {
console.log(this.name); // 'John'
}
};
// Constructor
function Person(name) {
this.name = name; // 'this' refers to new instance
}
// Explicit binding
function greet() {
console.log(`Hello, ${this.name}`);
}
const user = { name: 'Alice' };
greet.call(user); // "Hello, Alice"
greet.apply(user); // "Hello, Alice"
const boundGreet = greet.bind(user);
boundGreet(); // "Hello, Alice"
// Arrow functions (lexical this)
const obj2 = {
name: 'Bob',
greet() {
setTimeout(() => {
console.log(this.name); // 'Bob' - arrow function uses outer 'this'
}, 1000);
}
};
// Event handlers
button.addEventListener('click', function() {
console.log(this); // button element
});
button.addEventListener('click', () => {
console.log(this); // outer 'this', not button
});Functional Programming
Pure Functions
// Pure function - same input always produces same output
function add(a, b) {
return a + b;
}
// Impure function - depends on external state
let total = 0;
function addToTotal(value) {
total += value; // Modifies external state
return total;
}
// Pure function example
function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
// Impure - modifies input
function addItem(cart, item) {
cart.push(item); // Mutates cart
return cart;
}
// Pure - returns new array
function addItemPure(cart, item) {
return [...cart, item]; // Creates new array
}Higher-Order Functions
// Function that returns a function
function createGreeter(greeting) {
return function(name) {
return `${greeting}, ${name}!`;
};
}
const sayHello = createGreeter('Hello');
const sayHi = createGreeter('Hi');
console.log(sayHello('John')); // "Hello, John!"
console.log(sayHi('Alice')); // "Hi, Alice!"
// Function that takes a function as argument
function repeat(n, action) {
for (let i = 0; i < n; i++) {
action(i);
}
}
repeat(3, console.log); // 0, 1, 2
// Compose functions
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);
const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x * x;
const compute = compose(square, double, addOne);
console.log(compute(3)); // ((3 + 1) * 2)² = 64
// Pipe functions (left to right)
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
const compute2 = pipe(addOne, double, square);
console.log(compute2(3)); // ((3 + 1) * 2)² = 64Array Methods
const users = [
{ id: 1, name: 'John', age: 30, active: true },
{ id: 2, name: 'Alice', age: 25, active: false },
{ id: 3, name: 'Bob', age: 35, active: true },
{ id: 4, name: 'Charlie', age: 28, active: true }
];
// map - transform each element
const names = users.map(user => user.name);
// ['John', 'Alice', 'Bob', 'Charlie']
// filter - select elements
const activeUsers = users.filter(user => user.active);
// [{ id: 1, ... }, { id: 3, ... }, { id: 4, ... }]
// reduce - accumulate value
const totalAge = users.reduce((sum, user) => sum + user.age, 0);
// 118
// find - first matching element
const user = users.find(user => user.id === 2);
// { id: 2, name: 'Alice', ... }
// some - at least one matches
const hasInactive = users.some(user => !user.active);
// true
// every - all match
const allActive = users.every(user => user.active);
// false
// sort - order elements
const sortedByAge = [...users].sort((a, b) => a.age - b.age);
// flatMap - map and flatten
const tags = [
{ id: 1, tags: ['js', 'react'] },
{ id: 2, tags: ['vue', 'css'] }
];
const allTags = tags.flatMap(item => item.tags);
// ['js', 'react', 'vue', 'css']
// Chaining methods
const result = users
.filter(user => user.active)
.map(user => ({ ...user, ageGroup: user.age >= 30 ? 'senior' : 'junior' }))
.sort((a, b) => b.age - a.age);Currying
// Regular function
function add(a, b, c) {
return a + b + c;
}
// Curried function
function addCurried(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
// Arrow function version
const addCurriedArrow = a => b => c => a + b + c;
console.log(addCurried(1)(2)(3)); // 6
// Partial application
const add5 = addCurried(5);
const add5And10 = add5(10);
console.log(add5And10(3)); // 18
// Practical example
const multiply = a => b => a * b;
const double = multiply(2);
const triple = multiply(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
// Generic curry function
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
};
}
};
}
const sum = (a, b, c) => a + b + c;
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // 6
console.log(curriedSum(1, 2)(3)); // 6
console.log(curriedSum(1)(2, 3)); // 6Design Patterns
Module Pattern
const Calculator = (function() {
// Private variables
let result = 0;
// Private function
function log(operation, value) {
console.log(`${operation}: ${value}`);
}
// Public API
return {
add(value) {
result += value;
log('Add', value);
return this;
},
subtract(value) {
result -= value;
log('Subtract', value);
return this;
},
multiply(value) {
result *= value;
log('Multiply', value);
return this;
},
getResult() {
return result;
},
reset() {
result = 0;
return this;
}
};
})();
Calculator.add(5).multiply(2).subtract(3);
console.log(Calculator.getResult()); // 7Observer Pattern
class EventEmitter {
constructor() {
this.events = {};
}
on(event, listener) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(listener);
return this;
}
emit(event, ...args) {
if (this.events[event]) {
this.events[event].forEach(listener => {
listener(...args);
});
}
return this;
}
off(event, listenerToRemove) {
if (this.events[event]) {
this.events[event] = this.events[event].filter(
listener => listener !== listenerToRemove
);
}
return this;
}
once(event, listener) {
const onceWrapper = (...args) => {
listener(...args);
this.off(event, onceWrapper);
};
return this.on(event, onceWrapper);
}
}
// Usage
const emitter = new EventEmitter();
emitter.on('user:login', (user) => {
console.log(`${user.name} logged in`);
});
emitter.on('user:login', (user) => {
console.log(`Welcome, ${user.name}!`);
});
emitter.emit('user:login', { name: 'John' });
// "John logged in"
// "Welcome, John!"Singleton Pattern
class Database {
constructor() {
if (Database.instance) {
return Database.instance;
}
this.connection = null;
Database.instance = this;
}
connect() {
if (!this.connection) {
this.connection = 'Connected to database';
console.log(this.connection);
}
return this.connection;
}
disconnect() {
this.connection = null;
console.log('Disconnected from database');
}
}
const db1 = new Database();
const db2 = new Database();
console.log(db1 === db2); // true - same instanceFactory Pattern
class Car {
constructor(options) {
this.doors = options.doors || 4;
this.state = options.state || 'brand new';
this.color = options.color || 'silver';
}
}
class Truck {
constructor(options) {
this.state = options.state || 'used';
this.wheelSize = options.wheelSize || 'large';
this.color = options.color || 'blue';
}
}
class VehicleFactory {
createVehicle(type, options) {
switch(type) {
case 'car':
return new Car(options);
case 'truck':
return new Truck(options);
default:
throw new Error('Unknown vehicle type');
}
}
}
const factory = new VehicleFactory();
const car = factory.createVehicle('car', { color: 'red', doors: 2 });
const truck = factory.createVehicle('truck', { wheelSize: 'medium' });Error Handling
Try-Catch
// Basic try-catch
try {
const data = JSON.parse(invalidJSON);
} catch (error) {
console.error('Parse error:', error.message);
} finally {
console.log('Cleanup');
}
// Custom errors
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = 'ValidationError';
}
}
function validateUser(user) {
if (!user.email) {
throw new ValidationError('Email is required');
}
if (!user.name) {
throw new ValidationError('Name is required');
}
return true;
}
try {
validateUser({ name: 'John' });
} catch (error) {
if (error instanceof ValidationError) {
console.error('Validation failed:', error.message);
} else {
console.error('Unknown error:', error);
}
}
// Async error handling
async function fetchData() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Fetch failed:', error);
throw error; // Re-throw for caller to handle
}
}
// Error boundary pattern
async function safeExecute(fn, fallback) {
try {
return await fn();
} catch (error) {
console.error('Error:', error);
return fallback;
}
}
const data = await safeExecute(
() => fetchData(),
{ default: 'data' }
);Performance Optimization
Debounce dan Throttle
// Debounce - delay execution until after wait time
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Usage
const searchInput = document.querySelector('#search');
const debouncedSearch = debounce((value) => {
console.log('Searching for:', value);
// API call here
}, 500);
searchInput.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});
// Throttle - limit execution rate
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Usage
const throttledScroll = throttle(() => {
console.log('Scroll position:', window.scrollY);
}, 1000);
window.addEventListener('scroll', throttledScroll);Memoization
// Simple memoization
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
// Expensive calculation
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
const memoizedFib = memoize(fibonacci);
console.time('First call');
console.log(memoizedFib(40)); // Slow
console.timeEnd('First call');
console.time('Second call');
console.log(memoizedFib(40)); // Fast (cached)
console.timeEnd('Second call');Latihan Praktis
Project 1: API Client
Buat API client dengan:
- Fetch wrapper dengan error handling
- Request/response interceptors
- Retry logic
- Caching
- Loading states
Project 2: Event System
Buat event system dengan:
- Subscribe/unsubscribe
- Once listeners
- Event namespacing
- Async event handlers
- Error handling
Project 3: Data Processor
Buat data processor dengan:
- Functional programming patterns
- Array transformations
- Data validation
- Error handling
- Performance optimization
Kesimpulan
JavaScript modern adalah bahasa yang powerful dan flexible. Dengan menguasai async programming, functional programming, dan design patterns, Anda dapat membangun aplikasi yang robust, maintainable, dan performant.