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 adalah 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 singkat: 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 error ini 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 adalah lima root cause yang paling sering menyebabkan error ini, diurutkan dari yang paling banyak ditemui di lapangan:

  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 Memastikan Penyebab Mana yang Kamu Alami

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

Output yang diharapkan: 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);

Output yang diharapkan: 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. Best Practice: 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