Bahasa Indonesia

Jelajahi JavaScript Async Iterator Helpers untuk merevolusi pemrosesan stream. Pelajari cara menangani aliran data asinkron secara efisien dengan map, filter, take, drop, dan lainnya.

JavaScript Async Iterator Helpers: Pemrosesan Stream yang Andal untuk Aplikasi Modern

Dalam pengembangan JavaScript modern, berurusan dengan aliran data asinkron adalah persyaratan umum. Baik Anda mengambil data dari API, memproses file besar, atau menangani peristiwa waktu-nyata, mengelola data asinkron secara efisien sangatlah penting. JavaScript Async Iterator Helpers menyediakan cara yang andal dan elegan untuk memproses aliran ini, menawarkan pendekatan fungsional dan dapat disusun untuk manipulasi data.

Apa itu Async Iterator dan Async Iterable?

Sebelum mendalami Async Iterator Helpers, mari kita pahami konsep dasarnya: Async Iterator dan Async Iterable.

Async Iterable adalah objek yang mendefinisikan cara untuk melakukan iterasi secara asinkron terhadap nilainya. Ini dilakukan dengan mengimplementasikan metode @@asyncIterator, yang mengembalikan sebuah Async Iterator.

Async Iterator adalah objek yang menyediakan metode next(). Metode ini mengembalikan promise yang me-resolve ke objek dengan dua properti:

Berikut adalah contoh sederhana:


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 500)); // Simulate an asynchronous operation
    yield i;
  }
}

const asyncIterable = generateSequence(5);

(async () => {
  for await (const value of asyncIterable) {
    console.log(value); // Output: 1, 2, 3, 4, 5 (with 500ms delay between each)
  }
})();

Dalam contoh ini, generateSequence adalah fungsi generator asinkron yang menghasilkan urutan angka secara asinkron. Loop for await...of digunakan untuk mengonsumsi nilai dari async iterable.

Memperkenalkan Async Iterator Helpers

Async Iterator Helpers memperluas fungsionalitas Async Iterator, menyediakan serangkaian metode untuk mengubah, menyaring, dan memanipulasi aliran data asinkron. Mereka memungkinkan gaya pemrograman fungsional dan dapat disusun, membuatnya lebih mudah untuk membangun pipeline pemrosesan data yang kompleks.

Async Iterator Helpers inti meliputi:

Mari kita jelajahi setiap helper dengan contoh.

map()

Helper map() mengubah setiap elemen dari async iterable menggunakan fungsi yang disediakan. Ini mengembalikan async iterable baru dengan nilai-nilai yang telah diubah.


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(5);

const doubledIterable = asyncIterable.map(x => x * 2);

(async () => {
  for await (const value of doubledIterable) {
    console.log(value); // Output: 2, 4, 6, 8, 10 (with 100ms delay)
  }
})();

Dalam contoh ini, map(x => x * 2) menggandakan setiap angka dalam urutan.

filter()

Helper filter() memilih elemen dari async iterable berdasarkan kondisi yang disediakan (fungsi predikat). Ini mengembalikan async iterable baru yang hanya berisi elemen yang memenuhi kondisi.


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(10);

const evenNumbersIterable = asyncIterable.filter(x => x % 2 === 0);

(async () => {
  for await (const value of evenNumbersIterable) {
    console.log(value); // Output: 2, 4, 6, 8, 10 (with 100ms delay)
  }
})();

Dalam contoh ini, filter(x => x % 2 === 0) hanya memilih angka genap dari urutan.

take()

Helper take() mengembalikan N elemen pertama dari async iterable. Ini mengembalikan async iterable baru yang hanya berisi jumlah elemen yang ditentukan.


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(5);

const firstThreeIterable = asyncIterable.take(3);

(async () => {
  for await (const value of firstThreeIterable) {
    console.log(value); // Output: 1, 2, 3 (with 100ms delay)
  }
})();

Dalam contoh ini, take(3) memilih tiga angka pertama dari urutan.

drop()

Helper drop() melewatkan N elemen pertama dari async iterable dan mengembalikan sisanya. Ini mengembalikan async iterable baru yang berisi elemen yang tersisa.


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(5);

const afterFirstTwoIterable = asyncIterable.drop(2);

(async () => {
  for await (const value of afterFirstTwoIterable) {
    console.log(value); // Output: 3, 4, 5 (with 100ms delay)
  }
})();

Dalam contoh ini, drop(2) melewatkan dua angka pertama dari urutan.

toArray()

Helper toArray() mengonsumsi seluruh async iterable dan mengumpulkan semua elemen ke dalam sebuah array. Ini mengembalikan promise yang me-resolve ke sebuah array yang berisi semua elemen.


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(5);

(async () => {
  const numbersArray = await asyncIterable.toArray();
  console.log(numbersArray); // Output: [1, 2, 3, 4, 5]
})();

Dalam contoh ini, toArray() mengumpulkan semua angka dari urutan ke dalam sebuah array.

forEach()

Helper forEach() menjalankan fungsi yang disediakan sekali untuk setiap elemen dalam async iterable. Ini *tidak* mengembalikan async iterable baru, melainkan menjalankan fungsi sebagai efek samping. Ini bisa berguna untuk melakukan operasi seperti logging atau memperbarui UI.


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(3);

(async () => {
  await asyncIterable.forEach(value => {
    console.log("Value:", value);
  });
  console.log("forEach completed");
})();
// Output: Value: 1, Value: 2, Value: 3, forEach completed

some()

Helper some() menguji apakah setidaknya satu elemen dalam async iterable lolos dari tes yang diimplementasikan oleh fungsi yang disediakan. Ini mengembalikan promise yang me-resolve ke nilai boolean (true jika setidaknya satu elemen memenuhi kondisi, false jika sebaliknya).


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(5);

(async () => {
  const hasEvenNumber = await asyncIterable.some(x => x % 2 === 0);
  console.log("Has even number:", hasEvenNumber); // Output: Has even number: true
})();

every()

Helper every() menguji apakah semua elemen dalam async iterable lolos dari tes yang diimplementasikan oleh fungsi yang disediakan. Ini mengembalikan promise yang me-resolve ke nilai boolean (true jika semua elemen memenuhi kondisi, false jika sebaliknya).


async function* generateSequence(end) {
  for (let i = 2; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(4);

(async () => {
  const areAllEven = await asyncIterable.every(x => x % 2 === 0);
  console.log("Are all even:", areAllEven); // Output: Are all even: true
})();

find()

Helper find() mengembalikan elemen pertama dalam async iterable yang memenuhi fungsi pengujian yang disediakan. Jika tidak ada nilai yang memenuhi fungsi pengujian, undefined akan dikembalikan. Ini mengembalikan promise yang me-resolve ke elemen yang ditemukan atau undefined.


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(5);

(async () => {
  const firstEven = await asyncIterable.find(x => x % 2 === 0);
  console.log("First even number:", firstEven); // Output: First even number: 2
})();

reduce()

Helper reduce() menjalankan fungsi callback "reducer" yang disediakan pengguna pada setiap elemen dari async iterable, secara berurutan, dengan meneruskan nilai kembali dari perhitungan pada elemen sebelumnya. Hasil akhir dari menjalankan reducer di semua elemen adalah satu nilai tunggal. Ini mengembalikan promise yang me-resolve ke nilai terakumulasi akhir.


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(5);

(async () => {
  const sum = await asyncIterable.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
  console.log("Sum:", sum); // Output: Sum: 15
})();

Contoh Praktis dan Kasus Penggunaan

Async Iterator Helpers sangat berharga dalam berbagai skenario. Mari kita jelajahi beberapa contoh praktis:

1. Memproses Data dari API Streaming

Bayangkan Anda sedang membangun dasbor visualisasi data waktu-nyata yang menerima data dari API streaming. API tersebut mengirimkan pembaruan secara terus-menerus, dan Anda perlu memproses pembaruan ini untuk menampilkan informasi terbaru.


async function* fetchDataFromAPI(url) {
  let response = await fetch(url);

  if (!response.body) {
    throw new Error("ReadableStream not supported in this environment");
  }

  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) {
        break;
      }
      const chunk = decoder.decode(value);
      // Assuming the API sends JSON objects separated by newlines
      const lines = chunk.split('\n');
      for (const line of lines) {
        if (line.trim() !== '') {
          yield JSON.parse(line);
        }
      }
    }
  } finally {
    reader.releaseLock();
  }
}

const apiURL = 'https://example.com/streaming-api'; // Replace with your API URL
const dataStream = fetchDataFromAPI(apiURL);

// Process the data stream
(async () => {
  for await (const data of dataStream.filter(item => item.type === 'metric').map(item => ({ timestamp: item.timestamp, value: item.value }))) {
    console.log('Processed Data:', data);
    // Update the dashboard with the processed data
  }
})();

Dalam contoh ini, fetchDataFromAPI mengambil data dari API streaming, mem-parsing objek JSON, dan menghasilkannya sebagai async iterable. Helper filter hanya memilih metrik, dan helper map mengubah data ke format yang diinginkan sebelum memperbarui dasbor.

2. Membaca dan Memproses File Besar

Misalkan Anda perlu memproses file CSV besar yang berisi data pelanggan. Alih-alih memuat seluruh file ke dalam memori, Anda dapat menggunakan Async Iterator Helpers untuk memprosesnya sedikit demi sedikit.


async function* readLinesFromFile(filePath) {
  const file = await fsPromises.open(filePath, 'r');

  try {
    let buffer = Buffer.alloc(1024);
    let fileOffset = 0;
    let remainder = '';

    while (true) {
      const { bytesRead } = await file.read(buffer, 0, buffer.length, fileOffset);
      if (bytesRead === 0) {
        if (remainder) {
          yield remainder;
        }
        break;
      }

      fileOffset += bytesRead;
      const chunk = buffer.toString('utf8', 0, bytesRead);
      const lines = chunk.split('\n');

      lines[0] = remainder + lines[0];
      remainder = lines.pop() || '';

      for (const line of lines) {
        yield line;
      }
    }
  } finally {
    await file.close();
  }
}

const filePath = './customer_data.csv'; // Replace with your file path
const lines = readLinesFromFile(filePath);

// Process the lines
(async () => {
  for await (const customerData of lines.drop(1).map(line => line.split(',')).filter(data => data[2] === 'USA')) {
    console.log('Customer from USA:', customerData);
    // Process customer data from the USA
  }
})();

Dalam contoh ini, readLinesFromFile membaca file baris per baris dan menghasilkan setiap baris sebagai async iterable. Helper drop(1) melewatkan baris header, helper map membagi baris menjadi kolom, dan helper filter hanya memilih pelanggan dari USA.

3. Menangani Peristiwa Waktu-Nyata

Async Iterator Helpers juga dapat digunakan untuk menangani peristiwa waktu-nyata dari sumber seperti WebSocket. Anda dapat membuat async iterable yang memancarkan peristiwa saat tiba dan kemudian menggunakan helper untuk memproses peristiwa ini.


async function* createWebSocketStream(url) {
  const ws = new WebSocket(url);

  yield new Promise((resolve, reject) => {
      ws.onopen = () => {
          resolve();
      };
      ws.onerror = (error) => {
          reject(error);
      };
  });

  try {
    while (ws.readyState === WebSocket.OPEN) {
      yield new Promise((resolve, reject) => {
        ws.onmessage = (event) => {
          resolve(JSON.parse(event.data));
        };
        ws.onerror = (error) => {
          reject(error);
        };
        ws.onclose = () => {
           resolve(null); // Resolve with null when connection closes
        }
      });

    }
  } finally {
    ws.close();
  }
}

const websocketURL = 'wss://example.com/events'; // Replace with your WebSocket URL
const eventStream = createWebSocketStream(websocketURL);

// Process the event stream
(async () => {
  for await (const event of eventStream.filter(event => event.type === 'user_login').map(event => ({ userId: event.userId, timestamp: event.timestamp }))) {
    console.log('User Login Event:', event);
    // Process user login event
  }
})();

Dalam contoh ini, createWebSocketStream membuat async iterable yang memancarkan peristiwa yang diterima dari WebSocket. Helper filter hanya memilih peristiwa login pengguna, dan helper map mengubah data ke format yang diinginkan.

Manfaat Menggunakan Async Iterator Helpers

Dukungan Browser dan Runtime

Async Iterator Helpers masih merupakan fitur yang relatif baru di JavaScript. Hingga akhir tahun 2024, mereka berada di Tahap 3 dari proses standardisasi TC39, yang berarti kemungkinan besar akan distandardisasi dalam waktu dekat. Namun, mereka belum didukung secara native di semua browser dan versi Node.js.

Dukungan Browser: Browser modern seperti Chrome, Firefox, Safari, dan Edge secara bertahap menambahkan dukungan untuk Async Iterator Helpers. Anda dapat memeriksa informasi kompatibilitas browser terbaru di situs web seperti Can I use... untuk melihat browser mana yang mendukung fitur ini.

Dukungan Node.js: Versi Node.js terbaru (v18 ke atas) menyediakan dukungan eksperimental untuk Async Iterator Helpers. Untuk menggunakannya, Anda mungkin perlu menjalankan Node.js dengan flag --experimental-async-iterator.

Polyfill: Jika Anda perlu menggunakan Async Iterator Helpers di lingkungan yang tidak mendukungnya secara native, Anda dapat menggunakan polyfill. Polyfill adalah sepotong kode yang menyediakan fungsionalitas yang hilang. Beberapa pustaka polyfill tersedia untuk Async Iterator Helpers; pilihan populer adalah pustaka core-js.

Mengimplementasikan Async Iterator Kustom

Meskipun Async Iterator Helpers menyediakan cara yang mudah untuk memproses async iterable yang ada, terkadang Anda mungkin perlu membuat async iterator kustom Anda sendiri. Ini memungkinkan Anda untuk menangani data dari berbagai sumber, seperti basis data, API, atau sistem file, dengan cara streaming.

Untuk membuat async iterator kustom, Anda perlu mengimplementasikan metode @@asyncIterator pada sebuah objek. Metode ini harus mengembalikan objek dengan metode next(). Metode next() harus mengembalikan promise yang me-resolve ke objek dengan properti value dan done.

Berikut adalah contoh async iterator kustom yang mengambil data dari API berpaginasi:


async function* fetchPaginatedData(baseURL) {
  let page = 1;
  let hasMore = true;

  while (hasMore) {
    const url = `${baseURL}?page=${page}`;
    const response = await fetch(url);
    const data = await response.json();

    if (data.results.length === 0) {
      hasMore = false;
      break;
    }

    for (const item of data.results) {
      yield item;
    }

    page++;
  }
}

const apiBaseURL = 'https://api.example.com/data'; // Replace with your API URL
const paginatedData = fetchPaginatedData(apiBaseURL);

// Process the paginated data
(async () => {
  for await (const item of paginatedData) {
    console.log('Item:', item);
    // Process the item
  }
})();

Dalam contoh ini, fetchPaginatedData mengambil data dari API berpaginasi, menghasilkan setiap item saat diambil. Async iterator menangani logika paginasi, membuatnya mudah untuk mengonsumsi data dengan cara streaming.

Tantangan dan Pertimbangan Potensial

Meskipun Async Iterator Helpers menawarkan banyak manfaat, penting untuk menyadari beberapa tantangan dan pertimbangan potensial:

Praktik Terbaik untuk Menggunakan Async Iterator Helpers

Untuk mendapatkan hasil maksimal dari Async Iterator Helpers, pertimbangkan praktik terbaik berikut:

Teknik Tingkat Lanjut

Menyusun Helper Kustom

Anda dapat membuat helper async iterator kustom Anda sendiri dengan menyusun helper yang ada atau membangun yang baru dari awal. Ini memungkinkan Anda untuk menyesuaikan fungsionalitas dengan kebutuhan spesifik Anda dan membuat komponen yang dapat digunakan kembali.


async function* takeWhile(asyncIterable, predicate) {
  for await (const value of asyncIterable) {
    if (!predicate(value)) {
      break;
    }
    yield value;
  }
}

// Example Usage:
async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

const asyncIterable = generateSequence(10);
const firstFive = takeWhile(asyncIterable, x => x <= 5);

(async () => {
  for await (const value of firstFive) {
    console.log(value);
  }
})();

Menggabungkan Beberapa Async Iterable

Anda dapat menggabungkan beberapa async iterable menjadi satu async iterable tunggal menggunakan teknik seperti zip atau merge. Ini memungkinkan Anda untuk memproses data dari beberapa sumber secara bersamaan.


async function* zip(asyncIterable1, asyncIterable2) {
    const iterator1 = asyncIterable1[Symbol.asyncIterator]();
    const iterator2 = asyncIterable2[Symbol.asyncIterator]();

    while (true) {
        const result1 = await iterator1.next();
        const result2 = await iterator2.next();

        if (result1.done || result2.done) {
            break;
        }

        yield [result1.value, result2.value];
    }
}

// Example Usage:
async function* generateSequence1(end) {
    for (let i = 1; i <= end; i++) {
        yield i;
    }
}

async function* generateSequence2(end) {
    for (let i = 10; i <= end + 9; i++) {
        yield i;
    }
}

const iterable1 = generateSequence1(5);
const iterable2 = generateSequence2(5);

(async () => {
    for await (const [value1, value2] of zip(iterable1, iterable2)) {
        console.log(value1, value2);
    }
})();

Kesimpulan

JavaScript Async Iterator Helpers menyediakan cara yang andal dan elegan untuk memproses aliran data asinkron. Mereka menawarkan pendekatan fungsional dan dapat disusun untuk manipulasi data, membuatnya lebih mudah untuk membangun pipeline pemrosesan data yang kompleks. Dengan memahami konsep inti Async Iterator dan Async Iterable dan menguasai berbagai metode helper, Anda dapat secara signifikan meningkatkan efisiensi dan keterpeliharaan kode JavaScript asinkron Anda. Seiring dengan terus berkembangnya dukungan browser dan runtime, Async Iterator Helpers siap menjadi alat penting bagi pengembang JavaScript modern.