Ελληνικά

Εξερευνήστε τους JavaScript Async Iterator Helpers για να φέρετε την επανάσταση στην επεξεργασία ροών. Μάθετε πώς να διαχειρίζεστε αποτελεσματικά ασύγχρονες ροές δεδομένων με τις map, filter, take, drop και άλλες.

JavaScript Async Iterator Helpers: Ισχυρή Επεξεργασία Ροής για Σύγχρονες Εφαρμογές

Στη σύγχρονη ανάπτυξη JavaScript, η διαχείριση ασύγχρονων ροών δεδομένων είναι μια συνηθισμένη απαίτηση. Είτε ανακτάτε δεδομένα από ένα API, είτε επεξεργάζεστε μεγάλα αρχεία, είτε διαχειρίζεστε γεγονότα σε πραγματικό χρόνο, η αποτελεσματική διαχείριση των ασύγχρονων δεδομένων είναι κρίσιμη. Οι Async Iterator Helpers της JavaScript παρέχουν έναν ισχυρό και κομψό τρόπο επεξεργασίας αυτών των ροών, προσφέροντας μια λειτουργική και συνθετική προσέγγιση στον χειρισμό δεδομένων.

Τι είναι οι Async Iterators και οι Async Iterables;

Πριν εμβαθύνουμε στους Async Iterator Helpers, ας κατανοήσουμε τις υποκείμενες έννοιες: τους Async Iterators και τα Async Iterables.

Ένα Async Iterable είναι ένα αντικείμενο που ορίζει έναν τρόπο για ασύγχρονη επανάληψη πάνω στις τιμές του. Αυτό το κάνει υλοποιώντας τη μέθοδο @@asyncIterator, η οποία επιστρέφει έναν Async Iterator.

Ένας Async Iterator είναι ένα αντικείμενο που παρέχει μια μέθοδο next(). Αυτή η μέθοδος επιστρέφει μια promise που επιλύεται σε ένα αντικείμενο με δύο ιδιότητες:

Ακολουθεί ένα απλό παράδειγμα:


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)
  }
})();

Σε αυτό το παράδειγμα, η generateSequence είναι μια ασύγχρονη συνάρτηση-γεννήτρια (async generator function) που παράγει μια ακολουθία αριθμών ασύγχρονα. Ο βρόχος for await...of χρησιμοποιείται για την κατανάλωση των τιμών από το async iterable.

Εισαγωγή στους Async Iterator Helpers

Οι Async Iterator Helpers επεκτείνουν τη λειτουργικότητα των Async Iterators, παρέχοντας ένα σύνολο μεθόδων για τη μετατροπή, το φιλτράρισμα και τον χειρισμό ασύγχρονων ροών δεδομένων. Επιτρέπουν έναν λειτουργικό και συνθετικό τρόπο προγραμματισμού, καθιστώντας ευκολότερη την κατασκευή σύνθετων αγωγών επεξεργασίας δεδομένων.

Οι βασικοί Async Iterator Helpers περιλαμβάνουν:

Ας εξερευνήσουμε κάθε helper με παραδείγματα.

map()

Ο helper map() μετασχηματίζει κάθε στοιχείο του async iterable χρησιμοποιώντας μια παρεχόμενη συνάρτηση. Επιστρέφει ένα νέο async iterable με τις μετασχηματισμένες τιμές.


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)
  }
})();

Σε αυτό το παράδειγμα, η map(x => x * 2) διπλασιάζει κάθε αριθμό στην ακολουθία.

filter()

Ο helper filter() επιλέγει στοιχεία από το async iterable βάσει μιας παρεχόμενης συνθήκης (predicate function). Επιστρέφει ένα νέο async iterable που περιέχει μόνο τα στοιχεία που ικανοποιούν τη συνθήκη.


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)
  }
})();

Σε αυτό το παράδειγμα, η filter(x => x % 2 === 0) επιλέγει μόνο τους ζυγούς αριθμούς από την ακολουθία.

take()

Ο helper take() επιστρέφει τα πρώτα Ν στοιχεία από το async iterable. Επιστρέφει ένα νέο async iterable που περιέχει μόνο τον καθορισμένο αριθμό στοιχείων.


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)
  }
})();

Σε αυτό το παράδειγμα, η take(3) επιλέγει τους τρεις πρώτους αριθμούς από την ακολουθία.

drop()

Ο helper drop() παραλείπει τα πρώτα Ν στοιχεία από το async iterable και επιστρέφει τα υπόλοιπα. Επιστρέφει ένα νέο async iterable που περιέχει τα εναπομείναντα στοιχεία.


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)
  }
})();

Σε αυτό το παράδειγμα, η drop(2) παραλείπει τους δύο πρώτους αριθμούς από την ακολουθία.

toArray()

Ο helper toArray() καταναλώνει ολόκληρο το async iterable και συλλέγει όλα τα στοιχεία σε έναν πίνακα. Επιστρέφει μια promise που επιλύεται σε έναν πίνακα που περιέχει όλα τα στοιχεία.


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]
})();

Σε αυτό το παράδειγμα, η toArray() συλλέγει όλους τους αριθμούς από την ακολουθία σε έναν πίνακα.

forEach()

Ο helper forEach() εκτελεί μια παρεχόμενη συνάρτηση μία φορά για κάθε στοιχείο στο async iterable. *Δεν* επιστρέφει ένα νέο async iterable, αλλά εκτελεί τη συνάρτηση για τις παρενέργειές της. Αυτό μπορεί να είναι χρήσιμο για την εκτέλεση λειτουργιών όπως η καταγραφή (logging) ή η ενημέρωση ενός 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() ελέγχει αν τουλάχιστον ένα στοιχείο στο async iterable περνάει τον έλεγχο που υλοποιείται από την παρεχόμενη συνάρτηση. Επιστρέφει μια promise που επιλύεται σε μια boolean τιμή (true αν τουλάχιστον ένα στοιχείο ικανοποιεί τη συνθήκη, false διαφορετικά).


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() ελέγχει αν όλα τα στοιχεία στο async iterable περνούν τον έλεγχο που υλοποιείται από την παρεχόμενη συνάρτηση. Επιστρέφει μια promise που επιλύεται σε μια boolean τιμή (true αν όλα τα στοιχεία ικανοποιούν τη συνθήκη, false διαφορετικά).


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() επιστρέφει το πρώτο στοιχείο στο async iterable που ικανοποιεί την παρεχόμενη συνάρτηση ελέγχου. Αν καμία τιμή δεν ικανοποιεί τη συνάρτηση ελέγχου, επιστρέφεται undefined. Επιστρέφει μια promise που επιλύεται στο στοιχείο που βρέθηκε ή 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() εκτελεί μια συνάρτηση "reducer" που παρέχεται από τον χρήστη για κάθε στοιχείο του async iterable, με τη σειρά, περνώντας την τιμή επιστροφής από τον υπολογισμό στο προηγούμενο στοιχείο. Το τελικό αποτέλεσμα της εκτέλεσης του reducer σε όλα τα στοιχεία είναι μια μοναδική τιμή. Επιστρέφει μια promise που επιλύεται στην τελική συσσωρευμένη τιμή.


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
})();

Πρακτικά Παραδείγματα και Περιπτώσεις Χρήσης

Οι Async Iterator Helpers είναι πολύτιμοι σε μια ποικιλία σεναρίων. Ας εξερευνήσουμε μερικά πρακτικά παραδείγματα:

1. Επεξεργασία Δεδομένων από ένα Streaming API

Φανταστείτε ότι χτίζετε έναν πίνακα οπτικοποίησης δεδομένων σε πραγματικό χρόνο που λαμβάνει δεδομένα από ένα streaming API. Το API στέλνει συνεχώς ενημερώσεις και πρέπει να επεξεργαστείτε αυτές τις ενημερώσεις για να εμφανίσετε τις τελευταίες πληροφορίες.


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
  }
})();

Σε αυτό το παράδειγμα, η fetchDataFromAPI ανακτά δεδομένα από ένα streaming API, αναλύει τα αντικείμενα JSON και τα αποδίδει (yields) ως async iterable. Ο helper filter επιλέγει μόνο τα metrics και ο helper map μετατρέπει τα δεδομένα στην επιθυμητή μορφή πριν ενημερώσει τον πίνακα οργάνων.

2. Ανάγνωση και Επεξεργασία Μεγάλων Αρχείων

Ας υποθέσουμε ότι πρέπει να επεξεργαστείτε ένα μεγάλο αρχείο CSV που περιέχει δεδομένα πελατών. Αντί να φορτώσετε ολόκληρο το αρχείο στη μνήμη, μπορείτε να χρησιμοποιήσετε τους Async Iterator Helpers για να το επεξεργαστείτε τμηματικά.


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
  }
})();

Σε αυτό το παράδειγμα, η readLinesFromFile διαβάζει το αρχείο γραμμή προς γραμμή και αποδίδει κάθε γραμμή ως async iterable. Ο helper drop(1) παραλείπει τη γραμμή κεφαλίδας, ο helper map χωρίζει τη γραμμή σε στήλες και ο helper filter επιλέγει μόνο πελάτες από τις ΗΠΑ.

3. Διαχείριση Γεγονότων σε Πραγματικό Χρόνο

Οι Async Iterator Helpers μπορούν επίσης να χρησιμοποιηθούν για τη διαχείριση γεγονότων σε πραγματικό χρόνο από πηγές όπως τα WebSockets. Μπορείτε να δημιουργήσετε ένα async iterable που εκπέμπει γεγονότα καθώς φτάνουν και στη συνέχεια να χρησιμοποιήσετε τους helpers για την επεξεργασία αυτών των γεγονότων.


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
  }
})();

Σε αυτό το παράδειγμα, η createWebSocketStream δημιουργεί ένα async iterable που εκπέμπει γεγονότα που λαμβάνονται από ένα WebSocket. Ο helper filter επιλέγει μόνο τα γεγονότα σύνδεσης χρήστη και ο helper map μετατρέπει τα δεδομένα στην επιθυμητή μορφή.

Οφέλη από τη Χρήση των Async Iterator Helpers

Υποστήριξη σε Browsers και Runtimes

Οι Async Iterator Helpers είναι ακόμα ένα σχετικά νέο χαρακτηριστικό στη JavaScript. Στα τέλη του 2024, βρίσκονται στο Στάδιο 3 της διαδικασίας τυποποίησης TC39, πράγμα που σημαίνει ότι είναι πιθανό να τυποποιηθούν στο εγγύς μέλλον. Ωστόσο, δεν υποστηρίζονται ακόμη εγγενώς σε όλους τους browsers και τις εκδόσεις Node.js.

Υποστήριξη σε Browsers: Οι σύγχρονοι browsers όπως Chrome, Firefox, Safari και Edge προσθέτουν σταδιακά υποστήριξη για τους Async Iterator Helpers. Μπορείτε να ελέγξετε τις τελευταίες πληροφορίες συμβατότητας σε ιστότοπους όπως το Can I use... για να δείτε ποιοι browsers υποστηρίζουν αυτό το χαρακτηριστικό.

Υποστήριξη σε Node.js: Οι πρόσφατες εκδόσεις του Node.js (v18 και νεότερες) παρέχουν πειραματική υποστήριξη για τους Async Iterator Helpers. Για να τους χρησιμοποιήσετε, μπορεί να χρειαστεί να εκτελέσετε το Node.js με τη σημαία --experimental-async-iterator.

Polyfills: Εάν πρέπει να χρησιμοποιήσετε τους Async Iterator Helpers σε περιβάλλοντα που δεν τους υποστηρίζουν εγγενώς, μπορείτε να χρησιμοποιήσετε ένα polyfill. Ένα polyfill είναι ένα κομμάτι κώδικα που παρέχει την ελλιπή λειτουργικότητα. Υπάρχουν πολλές βιβλιοθήκες polyfill διαθέσιμες για τους Async Iterator Helpers· μια δημοφιλής επιλογή είναι η βιβλιοθήκη core-js.

Υλοποίηση Προσαρμοσμένων Async Iterators

Ενώ οι Async Iterator Helpers παρέχουν έναν βολικό τρόπο επεξεργασίας υπαρχόντων async iterables, μερικές φορές μπορεί να χρειαστεί να δημιουργήσετε τους δικούς σας προσαρμοσμένους async iterators. Αυτό σας επιτρέπει να χειρίζεστε δεδομένα από διάφορες πηγές, όπως βάσεις δεδομένων, APIs ή συστήματα αρχείων, με τρόπο ροής.

Για να δημιουργήσετε έναν προσαρμοσμένο async iterator, πρέπει να υλοποιήσετε τη μέθοδο @@asyncIterator σε ένα αντικείμενο. Αυτή η μέθοδος πρέπει να επιστρέφει ένα αντικείμενο με μια μέθοδο next(). Η μέθοδος next() πρέπει να επιστρέφει μια promise που επιλύεται σε ένα αντικείμενο με τις ιδιότητες value και done.

Ακολουθεί ένα παράδειγμα ενός προσαρμοσμένου async iterator που ανακτά δεδομένα από ένα σελιδοποιημένο API:


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
  }
})();

Σε αυτό το παράδειγμα, η fetchPaginatedData ανακτά δεδομένα από ένα σελιδοποιημένο API, αποδίδοντας κάθε στοιχείο καθώς ανακτάται. Ο async iterator χειρίζεται τη λογική της σελιδοποίησης, καθιστώντας εύκολη την κατανάλωση των δεδομένων με τρόπο ροής.

Πιθανές Προκλήσεις και Σκέψεις

Ενώ οι Async Iterator Helpers προσφέρουν πολλά οφέλη, είναι σημαντικό να γνωρίζετε ορισμένες πιθανές προκλήσεις και σκέψεις:

Βέλτιστες Πρακτικές για τη Χρήση των Async Iterator Helpers

Για να αξιοποιήσετε στο έπακρο τους Async Iterator Helpers, λάβετε υπόψη τις ακόλουθες βέλτιστες πρακτικές:

Προηγμένες Τεχνικές

Σύνθεση Προσαρμοσμένων Helpers

Μπορείτε να δημιουργήσετε τους δικούς σας προσαρμοσμένους async iterator helpers συνθέτοντας υπάρχοντες helpers ή χτίζοντας νέους από την αρχή. Αυτό σας επιτρέπει να προσαρμόσετε τη λειτουργικότητα στις συγκεκριμένες ανάγκες σας και να δημιουργήσετε επαναχρησιμοποιήσιμα στοιχεία.


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);
  }
})();

Συνδυασμός Πολλαπλών Async Iterables

Μπορείτε να συνδυάσετε πολλαπλά async iterables σε ένα ενιαίο async iterable χρησιμοποιώντας τεχνικές όπως το zip ή το merge. Αυτό σας επιτρέπει να επεξεργάζεστε δεδομένα από πολλαπλές πηγές ταυτόχρονα.


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);
    }
})();

Συμπέρασμα

Οι JavaScript Async Iterator Helpers παρέχουν έναν ισχυρό και κομψό τρόπο επεξεργασίας ασύγχρονων ροών δεδομένων. Προσφέρουν μια λειτουργική και συνθετική προσέγγιση στον χειρισμό δεδομένων, καθιστώντας ευκολότερη την κατασκευή σύνθετων αγωγών επεξεργασίας δεδομένων. Κατανοώντας τις βασικές έννοιες των Async Iterators και Async Iterables και κατακτώντας τις διάφορες μεθόδους helper, μπορείτε να βελτιώσετε σημαντικά την αποτελεσματικότητα και τη συντηρησιμότητα του ασύγχρονου κώδικα JavaScript σας. Καθώς η υποστήριξη σε browsers και runtimes συνεχίζει να αυξάνεται, οι Async Iterator Helpers είναι έτοιμοι να γίνουν ένα απαραίτητο εργαλείο για τους σύγχρονους προγραμματιστές JavaScript.