Arti Pesan Error Tersebut

Error ini muncul dalam lingkungan JavaScript (Node.js maupun browser) ketika kamu menulis kata kunci await di luar konteks yang diizinkan. Berikut kutipan pesan error asli yang biasanya muncul di konsol:

SyntaxError: await is only valid in async functions and the top level bodies of modules

Di beberapa versi Node.js yang lebih lama, pesannya bisa sedikit berbeda:

SyntaxError: await is only valid in async function

Arti singkatnyanya: Kamu menggunakan await di dalam fungsi yang tidak ditandai dengan async, atau di tingkat top-level sebuah file yang bukan module (ESM). JavaScript menolak menjalankan await karena mekanisme async/await membutuhkan konteks eksekusi khusus yang tahu cara menangani Promise.

Kenapa bisa muncul? Secara teknis, await adalah gula sintaksis (syntactic sugar) yang memberi tahu engine JavaScript untuk "menjeda" eksekusi fungsi saat ini sampai Promise di sebelah kanannya resolve. Namun, menjeda eksekusi hanya masuk akal jika fungsi pembungkusnya sendiri bersifat asinkron — yaitu mengembalikan Promise secara implisit. Jika fungsi pembungkusnya sinkron (fungsi biasa), engine tidak memiliki mekanisme untuk menyimpan state dan melanjutkan nanti. Inilah mengapa JavaScript memerlukan deklarasi async eksplisit sebelum await boleh dipakai.

Contoh paling sederhana yang memicu error:

// file: script.js (CommonJS)
function fetchData() {
  const response = await fetch('https://api.example.com/data');
  // SyntaxError: await is only valid in async functions...
  return response.json();
}

Di sini, fetchData bukan fungsi async, sehingga await di dalamnya ilegal.


Sumber Masalahnya

Berikut lima akar masalah yang paling sering memicu error ini, diurutkan dari yang paling umum terjadi:

  1. Lupa menambahkan async pada deklarasi fungsi. Ini adalah penyebab nomor satu. Kamu menulis await di dalam fungsi, tetapi lupa (atau tidak tahu) bahwa kata kunci async harus mendahului nama fungsi. Ini terjadi baik pada function declaration, function expression, maupun arrow function.

  2. Menggunakan await di top-level file CommonJS. Di Node.js, file yang menggunakan require() dan module.exports berjalan dalam mode CommonJS. Top-level await tidak diizinkan dalam CommonJS — hanya diizinkan di ES Modules (ESM). Jadi, menulis await langsung di badan file tanpa pembungkus async akan melempar error ini.

  3. Callback yang bukan async. Kamu menggunakan await di dalam callback fungsi pihak ketiga (misalnya Array.prototype.map, forEach, atau callback event listener), tetapi callback tersebut tidak ditandai async. Meskipun fungsi induknya async, callback adalah closure terpisah dengan konteks eksekusi sendiri.

  4. Arrow function implicit return tanpa async. Kamu menulis arrow function ringkas seperti items.map(item => await processItem(item)) dan lupa bahwa async harus ada: items.map(async item => await processItem(item)).

  5. Mixing CommonJS dan ESM secara tidak sengaja. Kamu menggunakan import/export di file dengan ekstensi .js (bukan .mjs) tanpa "type": "module" di package.json, sehingga Node.js memperlakukannya sebagai CommonJS, dan top-level await gagal.

Cara mempersempit kemungkinan penyebab

  • Periksa fungsi yang membungkus await. Sorot baris yang error, lalu naik ke deklarasi fungsi terdekat. Apakah ada kata async? Jika tidak, penyebabnya adalah nomor 1.
  • Periksa apakah await berada di top-level (di luar semua fungsi). Jika ya, periksa apakah file tersebut adalah ESM. Lihat package.json untuk "type": "module", atau periksa ekstensi file (.mjs). Jika bukan ESM, penyebabnya adalah nomor 2 atau 5.
  • Periksa apakah await ada di dalam callback. Jika await berada di fungsi anonim yang dilewatkan sebagai argumen, periksa apakah callback itu bertanda async. Jika tidak, penyebabnya adalah nomor 3 atau 4.

Memperbaikinya Langkah demi Langkah

Langkah 1: Tambahkan async pada Fungsi yang Memuat await

Jika await berada di dalam fungsi yang tidak bertanda async, solusi langsungnya adalah menambahkan async.

Sebelum (error):

function getUser(id) {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}

Sesudah (fixed):

async function getUser(id) {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}

Untuk arrow function:

Sebelum (error):

const getUser = (id) => {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
};

Sesudah (fixed):

const getUser = async (id) => {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
};

Hasil yang muncul: Tidak ada SyntaxError. Fungsi sekarang mengembalikan Promise, dan pemanggil harus menggunakan .then() atau await (dalam konteks async) untuk mendapatkan nilainya.

Verifikasi:

node your-file.js
# Jika tidak ada SyntaxError, langkah ini berhasil.

Langkah 2: Perbaiki Callback yang Mengandung await

Callback adalah fungsi terpisah. Ia tidak mewarisi async dari fungsi induknya. Kamu harus menandai callback secara eksplisit.

Sebelum (error):

async function processAll(items) {
  items.forEach((item) => {
    const result = await processItem(item);
    console.log(result);
  });
}

Sesudah (fixed):

async function processAll(items) {
  for (const item of items) {
    const result = await processItem(item);
    console.log(result);
  }
}

Atau jika tetap ingin menggunakan forEach, mark callback-nya sebagai async. Namun, hati-hati: forEach tidak menunggu Promise resolve, sehingga eksekusi bisa tidak berurutan. Untuk itu, for...of atau Promise.all dengan map lebih aman:

async function processAll(items) {
  await Promise.all(
    items.map(async (item) => {
      const result = await processItem(item);
      console.log(result);
    })
  );
}

Verifikasi: Jalankan dan pastikan semua item diproses secara berurutan (dengan for...of) atau paralel (dengan Promise.all), tanpa error.

Langkah 3: Aktifkan Top-Level Await untuk File ESM

Jika kamu menulis await di luar semua fungsi (top-level), kamu harus memastikan file tersebut adalah ES Module.

Opsi A — Gunakan .mjs sebagai ekstensi file:

mv script.js script.mjs
node script.mjs

Opsi B — Tambahkan "type": "module" di package.json:

# Periksa apakah package.json ada
cat package.json

Jika belum ada, buat:

npm init -y

Kemudian edit:

{
  "name": "my-project",
  "version": "1.0.0",
  "type": "module"
}

Sekarang semua file .js dalam proyek dianggap ESM, dan top-level await diizinkan:

// top-level await yang sekarang valid
const response = await fetch('https://api.example.com/data');
const data = response.json();
console.log(data);

Contoh output: File berjalan tanpa SyntaxError. Data ter-log ke konsol.

Verifikasi:

node script.mjs
# Atau
node script.js  # jika type: "module" sudah diset

Langkah 4: Jika Terpaksa CommonJS, Bungkus dengan IIFE Async

Jika kamu tidak bisa (atau tidak mau) mengonversi ke ESM, bungkus top-level await dalam Immediately Invoked Function Expression (IIFE) yang async:

Sebelum (error):

// script.js (CommonJS)
const data = await fetchSomething();

Sesudah (fixed):

// script.js (CommonJS)
(async () => {
  try {
    const data = await fetchSomething();
    console.log(data);
  } catch (error) {
    console.error('Error:', error);
  }
})();

Ini adalah pola yang sangat umum di proyek Node.js lama. IIFE langsung dipanggil, dan karena ia async, await di dalamnya legal. Jangan lupa blok try/catch karena error pada top-level await tanpa tangkapan akan menghentikan proses Node.js.

Verifikasi:

node script.js
# Data ter-log tanpa SyntaxError

Supaya Tidak Terulang

Fix permanen berarti kamu mengonfigurasi proyek sedemikian rupa sehingga error ini secara struktural tidak bisa muncul lagi, bukan hanya menambal satu file.

1. Migrasi ke ES Modules Secara Penuh

Jika kamu menggunakan Node.js 14 ke atas, ESM sudah stabil. Migrasi ke ESM mengizinkan top-level await dan membuat kode lebih konsisten.

# 1. Set type module
echo '{ "type": "module" }' > package.json

# 2. Ubah semua require() menjadi import
# Sebelum:
#   const express = require('express');
# Sesudah:
#   import express from 'express';

# 3. Ubah semua module.exports menjadi export
# Sebelum:
#   module.exports = myFunction;
# Sesudah:
#   export default myFunction;

# 4. Tambahkan ekstensi .js pada relative import
# Sebelum:
#   import utils from './utils';
# Sesudah:
#   import utils from './utils.js';

Perhatikan bahwa pada langkah ke-4, ESM di Node.js mengharuskan ekstensi file pada relative import. Ini adalah perbedaan penting dibanding CommonJS yang bisa mengabaikan ekstensi.

2. Konfigurasi ESLint untuk Mendeteksi Sebelum Runtime

ESLint dengan aturan yang tepat bisa menangkap masalah ini saat development, jauh sebelum kode dijalankan.

npm install --save-dev eslint
npx eslint --init

Tambahkan atau pastikan rule berikut aktif di .eslintrc.json:

{
  "parserOptions": {
    "ecmaVersion": 2022,
    "sourceType": "module"
  },
  "rules": {
    "require-await": "warn"
  }
}

Rule require-await akan memperingatkan jika ada fungsi bertanda async yang tidak mengandung await (sebaliknya), yang membantu kamu audit. Namun yang lebih relevan, gunakan plugin eslint-plugin-async-await atau cukup pastikan ecmaVersion benar sehingga parser mengenali konteks async/await dengan tepat dan melaporkan await di luar async function sebagai error parse.

3. Kebiasaan yang baik: Selalu Tandai Fungsi yang Mengandung Operasi Asinkron

Biasakan diri (dan tim) untuk secara konsisten:

  • Menandai setiap fungsi yang melakukan I/O (fetch, baca file, query database) dengan async.
  • Menangkap error dengan try/catch di dalam async function, atau melampirkan .catch() pada pemanggilan.
  • Tidak pernah menggunakan await di dalam forEach. Gunakan for...of untuk eksekusi sekuensial atau Promise.all dengan map untuk eksekusi paralel.

Mengapa forEach bermasalah? Karena callback yang diterima forEach bukanlah async function yang ditunggu oleh konteks pemanggil. Akibatnya, Promise berjalan "terlepas" (fire-and-forget) tanpa bisa di-await atau ditangkap error-nya dengan benar.

Contoh salah:

// ❌ await di dalam forEach — tidak ditunggu!
items.forEach(async (item) => {
  const result = await processItem(item);
  console.log(result);
});

Contoh benar (sekuensial):

// ✅ for...of — dieksekusi satu per satu
for (const item of items) {
  const result = await processItem(item);
  console.log(result);
}

Contoh benar (paralel):

// ✅ Promise.all + map — semua berjalan bersamaan
const results = await Promise.all(
  items.map(item => processItem(item))
);
console.log(results);

4. Gunakan Top-Level Await dengan Bijak

Jika kamu sudah berada di ESM (top-level await valid), pertimbangkan apakah benar-benar perlu menggunakannya di level teratas. Top-level await akan memblokir eksekusi modul lain yang mengimpornya hingga Promise selesai. Ini bisa menjadi bottleneck jika digunakan secara berlebihan.

Sebagai alternatif, ekspor async function dan biarkan konsumer yang menentukan kapan harus meng-await:

// Alih-alih top-level await:
// const data = await loadData();
// export default data;

// Lebih fleksibel:
export async function getData() {
  return await loadData();
}

5. Debugging: Lacak Posisi Error dengan Tepat

Saat error ini muncul, stack trace biasanya menunjuk baris tepat di mana await berada. Periksa:

  1. Fungsi pembungkus — apakah sudah diberi async?
  2. Scope arrow function — arrow function di dalam async function tidak otomatis menjadi async.
  3. Callback parameter — callback seperti yang diterima setTimeout, then, atau forEach adalah scope terpisah.
async function main() {
  // ❌ await di dalam callback setTimeout — bukan async!
  setTimeout(() => {
    const result = await fetch(url); // SyntaxError!
  }, 1000);

  // ✅ Deklarasikan callback sebagai async
  setTimeout(async () => {
    const result = await fetch(url); // OK
  }, 1000);
}

Supaya Tidak Terulang

Error SyntaxError: await is only valid in async functions and top level bodies of modules adalah mekanisme proteksi JavaScript agar await tidak digunakan di konteks yang tidak mendukung operasi asinkron. Solusinya selalu boils down ke salah satu dari:

  1. Bungkus dengan async — tambahkan keyword async pada fungsi yang mengandung await.
  2. Gunakan ESM — jika membutuhkan top-level await, migrasikan ke ES Modules.
  3. Gunakan .then()/.catch() — jika tidak ingin mengubah fungsi menjadi async.

Dengan memahami aturan scope async/await, menerapkan tooling seperti ESLint, dan mengikuti Kebiasaan yang baik di atas, kamu bisa menghindari error ini dan menulis kode asinkron yang bersih, aman, dan mudah di-maintain.