This TypeScript/JavaScript error occurs when you try to use the await keyword inside a function that hasn't been declared as async. The await keyword can only be used within async functions or at the top level of modules in modern environments.
The `await` keyword is used to pause the execution of an async function until a Promise is resolved. It's a fundamental part of JavaScript's asynchronous programming model. However, `await` can only be used in specific contexts: inside async functions or at the top level of ES modules. When TypeScript encounters an `await` expression inside a regular (non-async) function, it throws error TS1308. This is because the JavaScript runtime needs the function to be marked as `async` to properly handle the Promise-based control flow that `await` creates. This error is extremely common for developers learning async/await patterns or when converting callback-based code to Promise-based code. The error occurs at compile time in TypeScript, but in plain JavaScript, it manifests as a syntax error that prevents the code from running at all. The `async` keyword transforms a function to always return a Promise and allows the use of `await` within its body. Without this keyword, the runtime has no mechanism to pause execution and wait for Promise resolution.
The most common fix is to simply add async before the function declaration:
// BEFORE - Error TS1308
function fetchData() {
const response = await fetch('https://api.example.com/data');
return response.json();
}
// AFTER - Fixed by adding async
async function fetchData() {
const response = await fetch('https://api.example.com/data');
return response.json();
}This works for all function types:
// Arrow function
const getData = async () => {
const result = await somePromise();
return result;
};
// Method in a class
class DataService {
async fetchUser(id: number) {
const user = await fetch(`/api/users/${id}`);
return user.json();
}
}
// Async IIFE (Immediately Invoked Function Expression)
(async () => {
const data = await fetchData();
console.log(data);
})();When using await inside array methods like forEach, map, or filter, you need to make the callback function async:
// WRONG - forEach doesn't handle async properly
items.forEach((item) => {
await processItem(item); // Error: await in non-async function
});
// WRONG - even with async, forEach ignores promises
items.forEach(async (item) => {
await processItem(item); // Runs all in parallel, doesn't wait
});
// CORRECT - Use for...of loop for sequential processing
for (const item of items) {
await processItem(item);
}
// CORRECT - Use Promise.all for parallel processing
await Promise.all(items.map(async (item) => {
return await processItem(item);
}));
// CORRECT - Use for await...of for async iterables
for await (const item of asyncIterable) {
console.log(item);
}The key difference: forEach doesn't wait for async callbacks to complete, while for...of properly handles await.
Constructors cannot be async, so you need alternative patterns:
// WRONG - constructors cannot be async
class UserService {
constructor() {
this.user = await this.fetchUser(); // Error!
}
}
// CORRECT - Use a static factory method
class UserService {
private constructor(private user: User) {}
static async create() {
const user = await this.fetchUser();
return new UserService(user);
}
private static async fetchUser() {
const response = await fetch('/api/user');
return response.json();
}
}
// Usage
const service = await UserService.create();For React components (which can't be async):
// WRONG - React components can't be async
async function MyComponent() {
const data = await fetchData(); // Error!
return <div>{data}</div>;
}
// CORRECT - Use useEffect hook
function MyComponent() {
const [data, setData] = useState(null);
useEffect(() => {
async function loadData() {
const result = await fetchData();
setData(result);
}
loadData();
}, []);
return <div>{data}</div>;
}Modern JavaScript/TypeScript supports top-level await in modules. Update your tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "esnext",
"lib": ["ES2022"]
}
}Then you can use await at the top level:
// In a module (.ts or .mjs file)
const config = await fetch('/api/config').then(r => r.json());
const db = await connectDatabase(config);
export { db };Requirements for top-level await:
- TypeScript 3.8+
- module set to esnext or es2022
- target set to es2017 or higher
- Code must be in a module (has import/export statements)
Note: Top-level await blocks module execution until the Promise resolves, so use it sparingly.
If you absolutely cannot make a function async, use Promise chains with .then():
// When async is not an option
function getData() {
// Can't use: const result = await fetch('/api/data');
// Use .then() instead
return fetch('/api/data')
.then(response => response.json())
.then(data => {
console.log(data);
return data;
})
.catch(error => {
console.error(error);
throw error;
});
}
// Usage
getData().then(result => {
console.log(result);
});However, this is usually a sign you should make the function async instead. Promise chains are harder to read and maintain than async/await.
### Understanding Async Function Behavior
When you declare a function as async, TypeScript/JavaScript automatically wraps its return value in a Promise:
// These two are equivalent
async function getValue() {
return 42;
}
function getValue() {
return Promise.resolve(42);
}
// Both must be awaited
const value = await getValue(); // 42### Error Handling in Async Functions
Always handle errors in async functions, especially when using await:
async function fetchData() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Failed to fetch data:', error);
throw error; // Re-throw or handle gracefully
}
}### Async/Await vs Promise.all
For parallel operations, use Promise.all instead of sequential awaits:
// SLOW - Sequential (3 seconds total if each takes 1s)
const user = await fetchUser();
const posts = await fetchPosts();
const comments = await fetchComments();
// FAST - Parallel (1 second total)
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments()
]);### TypeScript Module vs Script Mode
If top-level await doesn't work, ensure your file is treated as a module:
// Add any import or export to make it a module
export {};
// Now top-level await works
const data = await fetchData();Without any import/export statements, TypeScript treats the file as a script, not a module, and top-level await is not allowed.
### Async Functions Return Promises
Remember that calling an async function always returns a Promise immediately:
async function doWork() {
await someTask();
return "done";
}
// Returns a Promise<string>, not a string
const result = doWork(); // Promise { <pending> }
// Must await to get the actual value
const actualResult = await doWork(); // "done"This is why forgetting await when calling async functions is a common bug—you get a Promise instead of the resolved value.
Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript
Type narrowing from typeof check produces 'never'
How to fix "Type narrowing produces never" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript