Om du har skrivit JavaScript i mer än några månader har du känt smärtan av djupt nästlade callbacks och snåriga .then()-kedjor. async/await, introducerat i ES2017, löste allt det — och ändå faller utvecklare fortfarande i samma tre eller fyra fällor med det. Låt oss gå igenom det ordentligt: hur det fungerar, hur man hanterar fel väl och de parallella exekveringsmönster som faktiskt spelar roll för prestanda.

Grunderna — async-funktioner och await

En async-funktion returnerar alltid ett Promise. Inuti den pausar await exekveringen tills det avvaktade Promise:t avgörs. Det är hela den mentala modellen:

js
async function fetchUserProfile(userId) {
  const response = await fetch(`https://api.example.com/users/${userId}`);
  const profile = await response.json();
  return profile; // wrapped in a Promise automatically
}

// Calling an async function gives you a Promise
const profilePromise = fetchUserProfile(42);
profilePromise.then(profile => console.log(profile.name));

// Or use await at the call site
const profile = await fetchUserProfile(42);
console.log(profile.name);

Nyckelordet await kan bara användas inuti en async-funktion — eller på toppnivån av en ES-modul (mer om det senare). Att använda det någon annanstans är ett syntaxfel.

Felhantering — det rätta och det felaktiga sättet

Det är här de flesta guider går fel. Instinkten är att linda allt i try/catch och kalla det klart. Det fungerar, men tomma catch-block är en kodlukt som maskerar verkliga buggar:

js
// ❌ Don't do this — silent failure, impossible to debug
async function loadConfig() {
  try {
    const res = await fetch('/api/config');
    return await res.json();
  } catch (err) {
    // swallowed — you'll never know what broke
  }
}

// ✅ Do this — handle errors explicitly, return something meaningful
async function loadConfig() {
  try {
    const res = await fetch('/api/config');
    if (!res.ok) {
      throw new Error(`Config fetch failed: ${res.status} ${res.statusText}`);
    }
    return await res.json();
  } catch (err) {
    console.error('loadConfig error:', err.message);
    return null; // caller can check for null
  }
}

Jag föredrar ett mönster där async-funktioner antingen returnerar null vid fel eller kastar avsiktliga fel. Vad jag undviker är att fånga ett fel, logga det och sedan returnera ett värde som får anroparen att tro att begäran lyckades.

Tips: Om du har flera await-uttryck i ett try-block hanterar ett enda catch alla av dem — men du förlorar kontexten om vilket som misslyckades. För komplexa flöden, överväg separata try/catch-block eller en hjälpare som await to(promise) som returnerar [error, data]-tupler.

Sekventiellt vs parallellt — prestandafällan

Det här är misstaget jag ser oftast i produktionskod. När du await:ar varje anrop efter varandra kör du dem sekventiellt — även när de är helt oberoende av varandra:

js
// ❌ Sequential — takes ~900ms total (300 + 300 + 300)
async function loadDashboard(userId) {
  const user     = await fetchUser(userId);       // 300ms
  const orders   = await fetchOrders(userId);     // 300ms
  const settings = await fetchSettings(userId);  // 300ms
  return { user, orders, settings };
}

// ✅ Parallel with Promise.all — takes ~300ms total
async function loadDashboard(userId) {
  const [user, orders, settings] = await Promise.all([
    fetchUser(userId),
    fetchOrders(userId),
    fetchSettings(userId)
  ]);
  return { user, orders, settings };
}

Promise.all() skickar alla tre begäranden i samma ögonblick och väntar på att alla ska slutföras. Om någon av dem avvisas avvisas hela saken. Det är vanligtvis vad du vill för dashboardladdning där all data krävs.

Promise.allSettled — när partiellt misslyckande är OK

Ibland vill du skicka flera begäranden och använda det som kommer tillbaka framgångsrikt, även om vissa misslyckas. Promise.allSettled() är byggd exakt för det:

js
async function loadWidgets(widgetIds) {
  const results = await Promise.allSettled(
    widgetIds.map(id => fetchWidget(id))
  );

  const widgets = [];
  const errors  = [];

  for (const result of results) {
    if (result.status === 'fulfilled') {
      widgets.push(result.value);
    } else {
      errors.push(result.reason.message);
    }
  }

  if (errors.length > 0) {
    console.warn('Some widgets failed to load:', errors);
  }

  return widgets; // return whatever succeeded
}

Det här mönstret är utmärkt för icke-kritiska UI-element — som ett sidofält med flera oberoende sektioner. Om en misslyckas visar du resten istället för att tömma hela sidan.

async i loopar — forEach-fällan

Det här har bränt alla minst en gång. Array.forEach() avvaktar inte async-callbacks — den skickar dem och fortsätter omedelbart. Loopen avslutas innan något av det asynkrona arbetet är klart:

js
const orderIds = [101, 102, 103, 104];

// ❌ forEach ignores async — all requests fire in parallel uncontrolled,
// and code after the forEach runs before any complete
orderIds.forEach(async (id) => {
  await processOrder(id); // NOT awaited by forEach
});
console.log('done?'); // prints before any order is processed

// ✅ for...of — sequential, fully awaited
for (const id of orderIds) {
  await processOrder(id);
}
console.log('done'); // prints after all orders are processed

// ✅ Parallel but controlled — all fire at once, await all completions
await Promise.all(orderIds.map(id => processOrder(id)));
console.log('done'); // prints after all orders are processed

Använd for...of när ordningen spelar roll eller när du behöver begränsa begäranden (bearbeta en i taget). Använd Promise.all(map(...)) när du vill ha maximal parallellism och inte behöver sekventiella garantier.

Toppnivå-await i ES-moduler

Sedan ES2022 kan du använda await på toppnivån av en ES-modul — ingen omslagsfunktion behövs. Det är stort för modulinitialisering som beror på asynkron data:

js
// config.js (ES module)
const response = await fetch('/api/runtime-config');
const config   = await response.json();

export const API_BASE_URL  = config.apiBaseUrl;
export const FEATURE_FLAGS = config.featureFlags;
js
// main.js — imports wait for config.js to fully resolve
import { API_BASE_URL, FEATURE_FLAGS } from './config.js';

console.log(API_BASE_URL); // guaranteed to be loaded

Toppnivå-await fungerar i Node.js 14.8+ med "type": "module" i package.json, och i alla moderna webbläsare via inbyggda ES-moduler. Den importerande modulens exekvering pausas tills den avvaktade modulen är fullständigt löst — vilket är precis den garanti du behöver.

En verklig pipeline — hämta, parsa och transformera

Här är en realistisk async-pipeline som kombinerar allt: hämtning från ett API, hantering av HTTP-fel, transformering av data och elegant återgång vid misslyckande:

js
async function getProductCatalog(categoryId) {
  // Step 1: fetch raw data
  const response = await fetch(
    `https://api.shop.example.com/categories/${categoryId}/products`,
    { headers: { Authorization: `Bearer ${getAuthToken()}` } }
  );

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

  // Step 2: parse JSON
  const raw = await response.json();

  // Step 3: transform into the shape your UI needs
  const products = raw.items.map(item => ({
    id:       item.product_id,
    name:     item.display_name,
    price:    (item.price_cents / 100).toFixed(2),
    inStock:  item.inventory_count > 0,
    imageUrl: item.media?.[0]?.url ?? '/images/placeholder.png'
  }));

  // Step 4: filter out anything that's been discontinued
  return products.filter(p => !p.discontinued);
}

// Usage
try {
  const catalog = await getProductCatalog('electronics');
  renderProductGrid(catalog);
} catch (err) {
  showErrorBanner(`Could not load products: ${err.message}`);
}

Användbara verktyg

När du felsöker async-kod som bearbetar JSON-data hjälper dessa verktyg: JSON Formatter för att inspektera API-svar, JSON Validator för att fånga felaktiga data innan de når din kod, och JS Formatter för att städa upp async-funktionskod. För hela async/await-specifikationen är MDN:s async-guide den mest grundliga referensen, och TC39-specifikationen täcker den exakta semantiken om du behöver den.

Sammanfattning

async/await gör asynkron JavaScript läsbar — men fällorna är verkliga. Kontrollera alltid response.ok innan du parsar, låt aldrig catch-block svälja fel tyst, använd Promise.all() för oberoende parallella anrop och håll dig borta från forEach med async-callbacks. Lås in de vanorna och din async-kod kommer att vara både snabb och felsökningsbar.