Hibuno

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 old

Spread 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)); // 15

Template 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)); // 15

Prototypes 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)² = 64

Array 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)); // 6

Design 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()); // 7

Observer 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 instance

Factory 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.

Langkah Selanjutnya

On this page