Svenska

Utforska JavaScript Async Iterator Helpers för att revolutionera strömbehandling. Lär dig hantera asynkrona dataströmmar effektivt med map, filter, take, drop med mera.

JavaScript Async Iterator Helpers: Kraftfull strömbehandling för moderna applikationer

I modern JavaScript-utveckling är hantering av asynkrona dataströmmar ett vanligt krav. Oavsett om du hämtar data från ett API, bearbetar stora filer eller hanterar realtidshändelser är det avgörande att hantera asynkron data effektivt. JavaScripts Async Iterator Helpers erbjuder ett kraftfullt och elegant sätt att bearbeta dessa strömmar, med en funktionell och komponerbar metod för datamanipulering.

Vad är Async Iterators och Async Iterables?

Innan vi dyker in i Async Iterator Helpers, låt oss förstå de underliggande koncepten: Async Iterators och Async Iterables.

En Async Iterable är ett objekt som definierar ett sätt att asynkront iterera över dess värden. Det gör detta genom att implementera metoden @@asyncIterator, som returnerar en Async Iterator.

En Async Iterator är ett objekt som tillhandahåller en next()-metod. Denna metod returnerar ett promise som resolverar till ett objekt med två egenskaper:

Här är ett enkelt exempel:


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

const asyncIterable = generateSequence(5);

(async () => {
  for await (const value of asyncIterable) {
    console.log(value); // Utskrift: 1, 2, 3, 4, 5 (med 500 ms fördröjning mellan varje)
  }
})();

I detta exempel är generateSequence en asynkron generatorfunktion som producerar en sekvens av nummer asynkront. Loopen for await...of används för att konsumera värdena från async iterable.

Introduktion till Async Iterator Helpers

Async Iterator Helpers utökar funktionaliteten hos Async Iterators genom att tillhandahålla en uppsättning metoder för att transformera, filtrera och manipulera asynkrona dataströmmar. De möjliggör en funktionell och komponerbar programmeringsstil, vilket gör det enklare att bygga komplexa databehandlingspipelines.

De centrala Async Iterator Helpers inkluderar:

Låt oss utforska varje hjälpare med exempel.

map()

Hjälparen map() transformerar varje element i en async iterable med hjälp av en angiven funktion. Den returnerar en ny async iterable med de transformerade värdena.


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); // Utskrift: 2, 4, 6, 8, 10 (med 100 ms fördröjning)
  }
})();

I detta exempel dubblar map(x => x * 2) varje nummer i sekvensen.

filter()

Hjälparen filter() väljer element från en async iterable baserat på ett angivet villkor (predikatfunktion). Den returnerar en ny async iterable som endast innehåller de element som uppfyller villkoret.


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); // Utskrift: 2, 4, 6, 8, 10 (med 100 ms fördröjning)
  }
})();

I detta exempel väljer filter(x => x % 2 === 0) endast ut de jämna talen från sekvensen.

take()

Hjälparen take() returnerar de första N elementen från en async iterable. Den returnerar en ny async iterable som endast innehåller det specificerade antalet element.


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); // Utskrift: 1, 2, 3 (med 100 ms fördröjning)
  }
})();

I detta exempel väljer take(3) de tre första numren från sekvensen.

drop()

Hjälparen drop() hoppar över de första N elementen från en async iterable och returnerar resten. Den returnerar en ny async iterable som innehåller de återstående elementen.


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); // Utskrift: 3, 4, 5 (med 100 ms fördröjning)
  }
})();

I detta exempel hoppar drop(2) över de två första numren från sekvensen.

toArray()

Hjälparen toArray() konsumerar hela async iterable och samlar alla element i en array. Den returnerar ett promise som resolverar till en array som innehåller alla element.


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); // Utskrift: [1, 2, 3, 4, 5]
})();

I detta exempel samlar toArray() alla nummer från sekvensen i en array.

forEach()

Hjälparen forEach() exekverar en angiven funktion en gång för varje element i en async iterable. Den returnerar *inte* en ny async iterable, den exekverar funktionen för dess sidoeffekter. Detta kan vara användbart för att utföra operationer som loggning eller uppdatering av ett 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");
})();
// Utskrift: Value: 1, Value: 2, Value: 3, forEach completed

some()

Hjälparen some() testar om minst ett element i en async iterable klarar testet som implementeras av den angivna funktionen. Den returnerar ett promise som resolverar till ett booleskt värde (true om minst ett element uppfyller villkoret, annars 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); // Utskrift: Has even number: true
})();

every()

Hjälparen every() testar om alla element i en async iterable klarar testet som implementeras av den angivna funktionen. Den returnerar ett promise som resolverar till ett booleskt värde (true om alla element uppfyller villkoret, annars 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); // Utskrift: Are all even: true
})();

find()

Hjälparen find() returnerar det första elementet i en async iterable som uppfyller den angivna testfunktionen. Om inga värden uppfyller testfunktionen returneras undefined. Den returnerar ett promise som resolverar till det funna elementet eller 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); // Utskrift: First even number: 2
})();

reduce()

Hjälparen reduce() exekverar en användardefinierad "reducer"-callbackfunktion på varje element i en async iterable, i ordning, och skickar in returvärdet från beräkningen på föregående element. Det slutliga resultatet av att köra reducern över alla element är ett enda värde. Den returnerar ett promise som resolverar till det slutliga ackumulerade värdet.


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); // Utskrift: Sum: 15
})();

Praktiska exempel och användningsfall

Async Iterator Helpers är värdefulla i en mängd olika scenarier. Låt oss utforska några praktiska exempel:

1. Bearbeta data från ett strömmande API

Föreställ dig att du bygger en instrumentpanel för datavisualisering i realtid som tar emot data från ett strömmande API. API:et skickar uppdateringar kontinuerligt, och du behöver bearbeta dessa uppdateringar för att visa den senaste informationen.


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

  if (!response.body) {
    throw new Error("ReadableStream stöds inte i denna miljö");
  }

  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);
      // Förutsatt att API:et skickar JSON-objekt separerade av nya rader
      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'; // Ersätt med din API-URL
const dataStream = fetchDataFromAPI(apiURL);

// Bearbeta dataströmmen
(async () => {
  for await (const data of dataStream.filter(item => item.type === 'metric').map(item => ({ timestamp: item.timestamp, value: item.value }))) {
    console.log('Bearbetad data:', data);
    // Uppdatera instrumentpanelen med den bearbetade datan
  }
})();

I detta exempel hämtar fetchDataFromAPI data från ett strömmande API, parsar JSON-objekten och yieldar dem som en async iterable. Hjälparen filter väljer endast ut mätvärden, och hjälparen map transformerar datan till önskat format innan instrumentpanelen uppdateras.

2. Läsa och bearbeta stora filer

Anta att du behöver bearbeta en stor CSV-fil som innehåller kunddata. Istället för att ladda hela filen i minnet kan du använda Async Iterator Helpers för att bearbeta den bit för bit.


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'; // Ersätt med din filsökväg
const lines = readLinesFromFile(filePath);

// Bearbeta raderna
(async () => {
  for await (const customerData of lines.drop(1).map(line => line.split(',')).filter(data => data[2] === 'USA')) {
    console.log('Kund från USA:', customerData);
    // Bearbeta kunddata från USA
  }
})();

I detta exempel läser readLinesFromFile filen rad för rad och yieldar varje rad som en async iterable. Hjälparen drop(1) hoppar över rubrikraden, hjälparen map delar upp raden i kolumner, och hjälparen filter väljer endast kunder från USA.

3. Hantera realtidshändelser

Async Iterator Helpers kan också användas för att hantera realtidshändelser från källor som WebSockets. Du kan skapa en async iterable som emitterar händelser när de anländer och sedan använda hjälparna för att bearbeta dessa händelser.


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); // Resolva med null när anslutningen stängs
        }
      });

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

const websocketURL = 'wss://example.com/events'; // Ersätt med din WebSocket-URL
const eventStream = createWebSocketStream(websocketURL);

// Bearbeta händelseströmmen
(async () => {
  for await (const event of eventStream.filter(event => event.type === 'user_login').map(event => ({ userId: event.userId, timestamp: event.timestamp }))) {
    console.log('Användarinloggningshändelse:', event);
    // Bearbeta användarinloggningshändelse
  }
})();

I detta exempel skapar createWebSocketStream en async iterable som emitterar händelser som tas emot från en WebSocket. Hjälparen filter väljer endast användarinloggningshändelser, och hjälparen map transformerar datan till önskat format.

Fördelar med att använda Async Iterator Helpers

Stöd i webbläsare och körtidsmiljöer

Async Iterator Helpers är fortfarande en relativt ny funktion i JavaScript. I slutet av 2024 är de i Steg 3 av TC39-standardiseringsprocessen, vilket innebär att de sannolikt kommer att standardiseras inom en snar framtid. De stöds dock ännu inte nativt i alla webbläsare och Node.js-versioner.

Webbläsarstöd: Moderna webbläsare som Chrome, Firefox, Safari och Edge lägger gradvis till stöd för Async Iterator Helpers. Du kan kontrollera den senaste informationen om webbläsarkompatibilitet på webbplatser som Can I use... för att se vilka webbläsare som stöder denna funktion.

Node.js-stöd: Senaste versioner av Node.js (v18 och senare) ger experimentellt stöd för Async Iterator Helpers. För att använda dem kan du behöva köra Node.js med flaggan --experimental-async-iterator.

Polyfills: Om du behöver använda Async Iterator Helpers i miljöer som inte stöder dem nativt kan du använda en polyfill. En polyfill är en kodbit som tillhandahåller den saknade funktionaliteten. Flera polyfill-bibliotek finns tillgängliga för Async Iterator Helpers; ett populärt alternativ är core-js-biblioteket.

Implementera anpassade Async Iterators

Även om Async Iterator Helpers erbjuder ett bekvämt sätt att bearbeta befintliga async iterables kan du ibland behöva skapa dina egna anpassade async iterators. Detta gör att du kan hantera data från olika källor, som databaser, API:er eller filsystem, på ett strömmande sätt.

För att skapa en anpassad async iterator måste du implementera metoden @@asyncIterator på ett objekt. Denna metod ska returnera ett objekt med en next()-metod. next()-metoden ska returnera ett promise som resolverar till ett objekt med egenskaperna value och done.

Här är ett exempel på en anpassad async iterator som hämtar data från ett paginerat 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'; // Ersätt med din API-URL
const paginatedData = fetchPaginatedData(apiBaseURL);

// Bearbeta paginerad data
(async () => {
  for await (const item of paginatedData) {
    console.log('Objekt:', item);
    // Bearbeta objektet
  }
})();

I detta exempel hämtar fetchPaginatedData data från ett paginerat API och yieldar varje objekt när det hämtas. Den asynkrona iteratorn hanterar pagineringslogiken, vilket gör det enkelt att konsumera datan på ett strömmande sätt.

Potentiella utmaningar och överväganden

Även om Async Iterator Helpers erbjuder många fördelar är det viktigt att vara medveten om några potentiella utmaningar och överväganden:

Bästa praxis för att använda Async Iterator Helpers

För att få ut det mesta av Async Iterator Helpers, överväg följande bästa praxis:

Avancerade tekniker

Komponera anpassade hjälpare

Du kan skapa dina egna anpassade async iterator helpers genom att komponera befintliga hjälpare eller bygga nya från grunden. Detta gör att du kan skräddarsy funktionaliteten efter dina specifika behov och skapa återanvändbara komponenter.


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

// Exempel på användning:
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);
  }
})();

Kombinera flera Async Iterables

Du kan kombinera flera async iterables till en enda async iterable med hjälp av tekniker som zip eller merge. Detta gör att du kan bearbeta data från flera källor samtidigt.


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

// Exempel på användning:
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);
    }
})();

Slutsats

JavaScript Async Iterator Helpers erbjuder ett kraftfullt och elegant sätt att bearbeta asynkrona dataströmmar. De erbjuder en funktionell och komponerbar metod för datamanipulering, vilket gör det enklare att bygga komplexa databehandlingspipelines. Genom att förstå de centrala koncepten för Async Iterators och Async Iterables och bemästra de olika hjälpmetoderna kan du avsevärt förbättra effektiviteten och underhållbarheten i din asynkrona JavaScript-kod. I takt med att stödet i webbläsare och körtidsmiljöer fortsätter att växa, är Async Iterator Helpers på väg att bli ett oumbärligt verktyg för moderna JavaScript-utvecklare.