Tutorial js

Async JavaScript: Promise dan Fetch

Async JavaScript memungkinkan program menjalankan tugas tanpa harus menunggu tugas lain selesai. Bayangkan seperti restoran yang bisa melayani banyak pelanggan secara bersamaan.

Mengapa Perlu Async?

Dalam JavaScript, beberapa operasi membutuhkan waktu:

  • Mengambil data dari server (API)
  • Membaca file besar
  • Menunggu user input
  • Timer/delay tertentu

Tanpa async, program akan "freeze" menunggu operasi selesai.

Synchronous vs Asynchronous

Synchronous (Sinkron) - Menunggu Antrian

console.log("1. Mulai program");
console.log("2. Proses data...");
console.log("3. Selesai");

// Output berurutan:
// 1. Mulai program
// 2. Proses data...
// 3. Selesai

Asynchronous (Async) - Tidak Menunggu

console.log("1. Mulai program");

// setTimeout = async operation
setTimeout(function () {
  console.log("2. Proses async selesai");
}, 2000);

console.log("3. Program lanjut");

// Output:
// 1. Mulai program
// 3. Program lanjut
// 2. Proses async selesai (setelah 2 detik)

Callback Function

Callback adalah function yang dipanggil setelah operasi async selesai.

function ambil_data_user(user_id, callback) {
  console.log("πŸ“‘ Mengambil data user...");

  // Simulasi delay network request
  setTimeout(function () {
    let user_data = {
      id: user_id,
      nama: "Ahmad Rizki",
      email: "ahmad@email.com",
      status: "active",
    };

    console.log("βœ… Data berhasil diambil");
    callback(user_data); // Panggil callback dengan data
  }, 2000);
}

function tampilkan_user(user) {
  console.log("=== INFO USER ===");
  console.log("ID:", user.id);
  console.log("Nama:", user.nama);
  console.log("Email:", user.email);
  console.log("Status:", user.status);
}

// Penggunaan callback
console.log("πŸš€ Memulai aplikasi...");
ambil_data_user(123, tampilkan_user);
console.log("⏳ Menunggu data...");

Callback Hell Problem

// Contoh callback hell (tidak disarankan)
ambil_data_user(123, function (user) {
  ambil_data_posts(user.id, function (posts) {
    ambil_data_comments(posts[0].id, function (comments) {
      ambil_data_likes(comments[0].id, function (likes) {
        console.log("πŸ˜΅β€πŸ’« Callback hell!");
        // Sangat sulit dibaca dan di-maintain
      });
    });
  });
});

Promise - Solusi Modern

Promise adalah janji bahwa operasi async akan selesai (berhasil atau gagal).

1. Membuat Promise

function ambil_cuaca(kota) {
  return new Promise(function (resolve, reject) {
    console.log(`🌀️ Mengambil data cuaca ${kota}...`);

    setTimeout(function () {
      // Simulasi random success/error
      let berhasil = Math.random() > 0.3;

      if (berhasil) {
        let data_cuaca = {
          kota: kota,
          suhu: Math.floor(Math.random() * 15) + 25, // 25-40Β°C
          kondisi: "Cerah",
          kelembaban: Math.floor(Math.random() * 40) + 60, // 60-100%
        };
        resolve(data_cuaca); // Promise berhasil
      } else {
        reject("❌ Gagal mengambil data cuaca"); // Promise gagal
      }
    }, 1500);
  });
}

2. Menggunakan Promise dengan .then() dan .catch()

ambil_cuaca("Jakarta")
  .then(function (data) {
    console.log("βœ… Data cuaca berhasil diambil:");
    console.log(`🌑️ Suhu: ${data.suhu}°C`);
    console.log(`β˜€οΈ Kondisi: ${data.kondisi}`);
    console.log(`πŸ’§ Kelembaban: ${data.kelembaban}%`);
  })
  .catch(function (error) {
    console.log("❌ Error:", error);
  })
  .finally(function () {
    console.log("πŸ”š Operasi selesai (berhasil atau gagal)");
  });

3. Chaining Promise

function cek_login(username, password) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (username === "admin" && password === "123456") {
        resolve({ id: 1, username: "admin", role: "administrator" });
      } else {
        reject("Username atau password salah");
      }
    }, 1000);
  });
}

function ambil_profile(user_id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        id: user_id,
        nama_lengkap: "Administrator System",
        last_login: new Date().toLocaleString(),
      });
    }, 800);
  });
}

function ambil_permissions(user_id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(["read", "write", "delete", "admin"]);
    }, 600);
  });
}

// Promise chaining
cek_login("admin", "123456")
  .then((user) => {
    console.log("βœ… Login berhasil:", user.username);
    return ambil_profile(user.id); // Return promise baru
  })
  .then((profile) => {
    console.log("βœ… Profile loaded:", profile.nama_lengkap);
    return ambil_permissions(profile.id);
  })
  .then((permissions) => {
    console.log("βœ… Permissions loaded:", permissions.join(", "));
    console.log("πŸŽ‰ Semua data berhasil dimuat!");
  })
  .catch((error) => {
    console.log("❌ Error dalam proses:", error);
  });

Async/Await - Syntax Modern

Async/await membuat kode async terlihat seperti kode sync.

1. Async Function

async function proses_login_modern() {
  try {
    console.log("πŸ” Memulai proses login...");

    // Menunggu hasil login
    let user = await cek_login("admin", "123456");
    console.log("βœ… Login berhasil:", user.username);

    // Menunggu profile
    let profile = await ambil_profile(user.id);
    console.log("βœ… Profile loaded:", profile.nama_lengkap);

    // Menunggu permissions
    let permissions = await ambil_permissions(profile.id);
    console.log("βœ… Permissions loaded:", permissions.join(", "));

    console.log("πŸŽ‰ Semua proses selesai!");

    return { user, profile, permissions };
  } catch (error) {
    console.log("❌ Error:", error);
    throw error; // Re-throw error jika perlu
  }
}

// Panggil async function
proses_login_modern()
  .then((result) => {
    console.log("πŸ“Š Hasil lengkap:", result);
  })
  .catch((error) => {
    console.log("🚫 Proses login gagal total");
  });

2. Multiple Async Operations

async function ambil_data_dashboard() {
  try {
    console.log("πŸ“Š Loading dashboard data...");

    // Jalankan beberapa operasi secara paralel
    let [cuaca, user_stats, notifikasi] = await Promise.all([
      ambil_cuaca("Jakarta"),
      ambil_user_statistics(),
      ambil_notifikasi(),
    ]);

    console.log("βœ… Semua data dashboard ready:");
    console.log("Cuaca:", cuaca.suhu + "Β°C");
    console.log("Total users:", user_stats.total_users);
    console.log("Notifikasi baru:", notifikasi.length);

    return { cuaca, user_stats, notifikasi };
  } catch (error) {
    console.log("❌ Gagal load dashboard:", error);
  }
}

// Helper functions
function ambil_user_statistics() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({
        total_users: 1250,
        active_today: 89,
        new_registrations: 12,
      });
    }, 1200);
  });
}

function ambil_notifikasi() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve([
        { id: 1, message: "User baru mendaftar", time: "5 menit lalu" },
        { id: 2, message: "Server maintenance", time: "1 jam lalu" },
        { id: 3, message: "Backup completed", time: "2 jam lalu" },
      ]);
    }, 900);
  });
}

// Test dashboard
ambil_data_dashboard();

Fetch API - HTTP Requests

Fetch adalah API modern untuk mengambil data dari server.

1. GET Request

async function ambil_data_posts() {
  try {
    console.log("πŸ“‘ Fetching posts from API...");

    // Fetch data dari API
    let response = await fetch(
      "https://jsonplaceholder.typicode.com/posts?_limit=5"
    );

    // Cek apakah request berhasil
    if (!response.ok) {
      throw new Error(`HTTP Error: ${response.status}`);
    }

    // Convert response ke JSON
    let posts = await response.json();

    console.log("βœ… Data berhasil diambil:");
    posts.forEach((post, index) => {
      console.log(`${index + 1}. ${post.title}`);
      console.log(`   ${post.body.substring(0, 50)}...`);
      console.log("---");
    });

    return posts;
  } catch (error) {
    console.log("❌ Error fetching data:", error.message);
  }
}

// Test fetch
ambil_data_posts();

2. POST Request

async function buat_post_baru(title, content, user_id) {
  try {
    console.log("πŸ“ Membuat post baru...");

    let response = await fetch("https://jsonplaceholder.typicode.com/posts", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        title: title,
        body: content,
        userId: user_id,
      }),
    });

    if (!response.ok) {
      throw new Error(`HTTP Error: ${response.status}`);
    }

    let new_post = await response.json();

    console.log("βœ… Post berhasil dibuat:");
    console.log("ID:", new_post.id);
    console.log("Title:", new_post.title);
    console.log("User ID:", new_post.userId);

    return new_post;
  } catch (error) {
    console.log("❌ Error creating post:", error.message);
  }
}

// Test POST
buat_post_baru(
  "Tutorial JavaScript Async",
  "Belajar async/await dengan mudah dan menyenangkan",
  1
);

3. Fetch dengan Error Handling

async function fetch_dengan_retry(url, max_retries = 3) {
  for (let attempt = 1; attempt <= max_retries; attempt++) {
    try {
      console.log(`πŸ”„ Percobaan ${attempt}/${max_retries}: ${url}`);

      let response = await fetch(url);

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      let data = await response.json();
      console.log(`βœ… Berhasil pada percobaan ${attempt}`);
      return data;
    } catch (error) {
      console.log(`❌ Percobaan ${attempt} gagal:`, error.message);

      if (attempt === max_retries) {
        console.log("🚫 Semua percobaan gagal");
        throw error;
      }

      // Wait sebelum retry
      await new Promise((resolve) => setTimeout(resolve, 1000 * attempt));
    }
  }
}

// Test retry mechanism
fetch_dengan_retry("https://jsonplaceholder.typicode.com/posts/1")
  .then((data) => console.log("πŸ“„ Data berhasil diambil:", data.title))
  .catch((error) => console.log("πŸ’₯ Gagal total:", error.message));

Contoh Praktis: Weather App

class WeatherApp {
  constructor() {
    this.api_key = "demo_key"; // Ganti dengan API key asli
    this.base_url = "https://api.openweathermap.org/data/2.5";
  }

  async ambil_cuaca_kota(nama_kota) {
    try {
      console.log(`🌀️ Mengambil cuaca ${nama_kota}...`);

      // Simulasi API call (karena kita tidak punya API key asli)
      await this.delay(1500);

      // Simulasi data cuaca
      let cuaca_data = {
        kota: nama_kota,
        negara: "ID",
        suhu: Math.floor(Math.random() * 10) + 25, // 25-35Β°C
        kondisi: this.random_kondisi(),
        kelembaban: Math.floor(Math.random() * 30) + 60, // 60-90%
        angin: Math.floor(Math.random() * 15) + 5, // 5-20 km/h
        update_time: new Date().toLocaleString(),
      };

      return cuaca_data;
    } catch (error) {
      throw new Error(`Gagal mengambil cuaca: ${error.message}`);
    }
  }

  async ambil_forecast_5_hari(nama_kota) {
    try {
      console.log(`πŸ“… Mengambil forecast 5 hari ${nama_kota}...`);

      await this.delay(2000);

      let forecast = [];
      for (let i = 1; i <= 5; i++) {
        let tanggal = new Date();
        tanggal.setDate(tanggal.getDate() + i);

        forecast.push({
          tanggal: tanggal.toLocaleDateString(),
          suhu_max: Math.floor(Math.random() * 8) + 28,
          suhu_min: Math.floor(Math.random() * 5) + 22,
          kondisi: this.random_kondisi(),
        });
      }

      return forecast;
    } catch (error) {
      throw new Error(`Gagal mengambil forecast: ${error.message}`);
    }
  }

  async tampilkan_info_lengkap(nama_kota) {
    try {
      console.log(`πŸš€ Memuat info cuaca lengkap untuk ${nama_kota}...\n`);

      // Ambil data cuaca dan forecast secara paralel
      let [cuaca_sekarang, forecast] = await Promise.all([
        this.ambil_cuaca_kota(nama_kota),
        this.ambil_forecast_5_hari(nama_kota),
      ]);

      // Tampilkan cuaca sekarang
      console.log("=== CUACA HARI INI ===");
      console.log(
        `πŸ“ Lokasi: ${cuaca_sekarang.kota}, ${cuaca_sekarang.negara}`
      );
      console.log(`🌑️ Suhu: ${cuaca_sekarang.suhu}°C`);
      console.log(`β˜€οΈ Kondisi: ${cuaca_sekarang.kondisi}`);
      console.log(`πŸ’§ Kelembaban: ${cuaca_sekarang.kelembaban}%`);
      console.log(`πŸ’¨ Angin: ${cuaca_sekarang.angin} km/h`);
      console.log(`πŸ• Update: ${cuaca_sekarang.update_time}\n`);

      // Tampilkan forecast
      console.log("=== FORECAST 5 HARI ===");
      forecast.forEach((hari, index) => {
        console.log(`${index + 1}. ${hari.tanggal}`);
        console.log(
          `   ${hari.suhu_min}Β°C - ${hari.suhu_max}Β°C, ${hari.kondisi}`
        );
      });

      return { cuaca_sekarang, forecast };
    } catch (error) {
      console.log("❌ Error:", error.message);
    }
  }

  // Helper methods
  delay(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  random_kondisi() {
    let kondisi_list = ["Cerah", "Berawan", "Hujan Ringan", "Mendung", "Panas"];
    return kondisi_list[Math.floor(Math.random() * kondisi_list.length)];
  }
}

// Test Weather App
let weather_app = new WeatherApp();

async function test_weather_app() {
  await weather_app.tampilkan_info_lengkap("Jakarta");
  console.log("\n" + "=".repeat(50) + "\n");
  await weather_app.tampilkan_info_lengkap("Bandung");
}

test_weather_app();

Error Handling Best Practices

class APIClient {
  constructor(base_url) {
    this.base_url = base_url;
    this.timeout = 5000; // 5 detik timeout
  }

  async request(endpoint, options = {}) {
    try {
      // Tambahkan timeout
      let controller = new AbortController();
      let timeout_id = setTimeout(() => controller.abort(), this.timeout);

      let response = await fetch(this.base_url + endpoint, {
        ...options,
        signal: controller.signal,
      });

      clearTimeout(timeout_id);

      // Handle HTTP errors
      if (!response.ok) {
        throw new APIError(`HTTP ${response.status}`, response.status);
      }

      return await response.json();
    } catch (error) {
      if (error.name === "AbortError") {
        throw new APIError("Request timeout", "TIMEOUT");
      }

      if (error instanceof APIError) {
        throw error;
      }

      throw new APIError("Network error", "NETWORK_ERROR");
    }
  }

  async get(endpoint) {
    return this.request(endpoint, { method: "GET" });
  }

  async post(endpoint, data) {
    return this.request(endpoint, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(data),
    });
  }
}

// Custom Error class
class APIError extends Error {
  constructor(message, code) {
    super(message);
    this.name = "APIError";
    this.code = code;
  }
}

// Penggunaan dengan proper error handling
async function demo_error_handling() {
  let api = new APIClient("https://jsonplaceholder.typicode.com");

  try {
    let posts = await api.get("/posts?_limit=3");
    console.log("βœ… Posts berhasil diambil:", posts.length, "items");
  } catch (error) {
    if (error instanceof APIError) {
      switch (error.code) {
        case "TIMEOUT":
          console.log("⏰ Request timeout - coba lagi");
          break;
        case "NETWORK_ERROR":
          console.log("🌐 Masalah koneksi internet");
          break;
        default:
          console.log("🚫 API Error:", error.message);
      }
    } else {
      console.log("πŸ’₯ Unexpected error:", error.message);
    }
  }
}

demo_error_handling();

Tips Async JavaScript

πŸ’‘ Selalu gunakan try-catch:

// ❌ Tanpa error handling
async function bad_example() {
  let data = await fetch("/api/data");
  return data.json(); // Bisa error!
}

// βœ… Dengan error handling
async function good_example() {
  try {
    let response = await fetch("/api/data");
    if (!response.ok) throw new Error("API Error");
    return await response.json();
  } catch (error) {
    console.log("Error:", error.message);
    return null;
  }
}

πŸ’‘ Gunakan Promise.all untuk operasi paralel:

// ❌ Sequential (lambat)
async function sequential() {
  let user = await fetch_user(); // 1 detik
  let posts = await fetch_posts(); // 1 detik
  let comments = await fetch_comments(); // 1 detik
  // Total: 3 detik
}

// βœ… Parallel (cepat)
async function parallel() {
  let [user, posts, comments] = await Promise.all([
    fetch_user(), // Semua jalan bersamaan
    fetch_posts(),
    fetch_comments(),
  ]);
  // Total: 1 detik
}

Ringkasan

Async JavaScript untuk:

  • Operasi yang membutuhkan waktu
  • Network requests (API calls)
  • File operations
  • Timer/delay operations

3 Cara handle async:

  • Callback - cara lama, bisa callback hell
  • Promise - modern, dengan .then()/.catch()
  • Async/Await - paling modern, mudah dibaca

Fetch API untuk:

  • HTTP requests ke server
  • GET, POST, PUT, DELETE data
  • RESTful API communication

Best practices:

  • Selalu handle errors
  • Gunakan timeout untuk requests
  • Parallel execution untuk performance

Siap eksplorasi ES6+ Features? Let's go modern! πŸš€