The 3 Core Behaviors:
1. async = Promise Factory:
javascript
async function anything() {
return "hello"; // Automatically becomes Promise.resolve("hello")
}
// ANY async function ALWAYS returns a promise, no exceptions2. await = Promise Unwrapper:
let result = await somePromise; // Pauses function, extracts promise value
// Think: "Stop here, wait for promise, give me the actual value"3. Error Transform Rule:
// These are identical:
await Promise.reject(new Error("oops"));
throw new Error("oops");
// await converts rejected promises to thrown errorsThe Mental Model - Function Transformation:
Before async/await (Promise Hell):
function getData() {
return fetch('/user')
.then(response => response.json())
.then(user => fetch(`/posts/${user.id}`))
.then(response => response.json())
.then(posts => posts.filter(p => p.published));
}After async/await (Readable Code):
async function getData() {
let response = await fetch('/user');
let user = await response.json();
let postsResponse = await fetch(`/posts/${user.id}`);
let posts = await postsResponse.json();
return posts.filter(p => p.published);
}Same behavior, but reads like synchronous code!
The await HALT Behavior:
What await actually does:
async function slowFunction() {
console.log('Start slow');
await new Promise(resolve => setTimeout(resolve, 3000)); // HALT HERE
console.log('End slow'); // UNREACHABLE until await resolves
}await = LITERAL FUNCTION PAUSE
- Function execution STOPS at await line
- Everything below await is unreachable until promise resolves
- Function exits and control goes back to caller
Your Exact Scenario Breakdown:
With await (your example):
console.log('Before'); // 1. Runs first (sync)
slowFunction(); // 2. Starts function
console.log('Start slow'); // 3. Runs (sync inside function)
await new Promise(...); // 4. HALT! Function pauses, exits
console.log('After'); // 5. Runs immediately (sync)
// 6. (3 seconds later) Promise resolves, function resumes
console.log('End slow'); // 7. Finally runsWithout await (your comparison):
console.log('Before'); // 1. Runs first
slowFunction(); // 2. Starts function
console.log('Start slow'); // 3. Runs
new Promise(resolve => setTimeout(...)); // 4. Creates promise, doesn't wait
console.log('End slow'); // 5. Runs immediately!
console.log('After'); // 6. Runs
// Promise resolves 3 seconds later (nobody cares)With .then (your other comparison):
console.log('Before');
slowFunction();
console.log('Start slow');
new Promise(resolve => setTimeout(...))
.then(() => console.log('Promise done')); // Goes to microtask queue
console.log('End slow'); // Runs immediately (not in .then)
console.log('After');
// Later: 'Promise done' runsThe Mental Rule:
await = Function Boundary
- Think of await as an invisible function exit
- Everything after await is like a
.then()callback - Function literally stops existing until promise resolves
Visual representation:
javascript
async function test() {
console.log('A');
await promise;
// ^^^ INVISIBLE WALL - function exits here
console.log('B'); // This is like .then(() => console.log('B'))
}The Key Difference:
await behavior:
javascript
// This code
async function f() {
console.log('1');
await promise;
console.log('2'); // Waits for promise
}
// Behaves like this
function f() {
console.log('1');
return promise.then(() => {
console.log('2');
});
}No await behavior:
javascript
function f() {
console.log('1');
promise; // Just creates promise, doesn't wait
console.log('2'); // Runs immediately
}Bottom Line:
await = hard stop. Think of it as the function temporarily dying at that line. Control goes back to caller, sync code continues, and the function resurrects later when the promise resolves. Everything after await is essentially a .then() callback in disguise!
