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 modulesDi beberapa versi Node.js yang lebih lama, pesannya bisa sedikit berbeda:
SyntaxError: await is only valid in async functionArti 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:
Lupa menambahkan
asyncpada deklarasi fungsi. Ini adalah penyebab nomor satu. Kamu menulisawaitdi dalam fungsi, tetapi lupa (atau tidak tahu) bahwa kata kunciasyncharus mendahului nama fungsi. Ini terjadi baik pada function declaration, function expression, maupun arrow function.Menggunakan
awaitdi top-level file CommonJS. Di Node.js, file yang menggunakanrequire()danmodule.exportsberjalan dalam mode CommonJS. Top-levelawaittidak diizinkan dalam CommonJS — hanya diizinkan di ES Modules (ESM). Jadi, menulisawaitlangsung di badan file tanpa pembungkus async akan melempar error ini.Callback yang bukan async. Kamu menggunakan
awaitdi dalam callback fungsi pihak ketiga (misalnyaArray.prototype.map,forEach, atau callback event listener), tetapi callback tersebut tidak ditandaiasync. Meskipun fungsi induknya async, callback adalah closure terpisah dengan konteks eksekusi sendiri.Arrow function implicit return tanpa
async. Kamu menulis arrow function ringkas sepertiitems.map(item => await processItem(item))dan lupa bahwaasyncharus ada:items.map(async item => await processItem(item)).Mixing CommonJS dan ESM secara tidak sengaja. Kamu menggunakan
import/exportdi file dengan ekstensi.js(bukan.mjs) tanpa"type": "module"dipackage.json, sehingga Node.js memperlakukannya sebagai CommonJS, dan top-levelawaitgagal.
Cara mempersempit kemungkinan penyebab
- Periksa fungsi yang membungkus
await. Sorot baris yang error, lalu naik ke deklarasi fungsi terdekat. Apakah ada kataasync? Jika tidak, penyebabnya adalah nomor 1. - Periksa apakah
awaitberada di top-level (di luar semua fungsi). Jika ya, periksa apakah file tersebut adalah ESM. Lihatpackage.jsonuntuk"type": "module", atau periksa ekstensi file (.mjs). Jika bukan ESM, penyebabnya adalah nomor 2 atau 5. - Periksa apakah
awaitada di dalam callback. Jikaawaitberada di fungsi anonim yang dilewatkan sebagai argumen, periksa apakah callback itu bertandaasync. 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.mjsOpsi B — Tambahkan "type": "module" di package.json:
# Periksa apakah package.json ada
cat package.jsonJika belum ada, buat:
npm init -yKemudian 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 disetLangkah 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 SyntaxErrorSupaya 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 --initTambahkan 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/catchdi dalam async function, atau melampirkan.catch()pada pemanggilan. - Tidak pernah menggunakan
awaitdi dalamforEach. Gunakanfor...ofuntuk eksekusi sekuensial atauPromise.alldenganmapuntuk 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:
- Fungsi pembungkus — apakah sudah diberi
async? - Scope arrow function — arrow function di dalam async function tidak otomatis menjadi async.
- Callback parameter — callback seperti yang diterima
setTimeout,then, atauforEachadalah 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:
- Bungkus dengan
async— tambahkan keywordasyncpada fungsi yang mengandungawait. - Gunakan ESM — jika membutuhkan top-level await, migrasikan ke ES Modules.
- 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.