new Promise()
ES6+Creates a new Promise object with an executor function that receives resolve and reject callbacks.
Syntax
new Promise(executor)Parameters
executor Function A function that receives resolve and reject functions as parameters
Return Value
A new Promise object
Examples
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('완료!');
}, 1000);
});
promise.then(value => console.log(value)); 📌 When to Use
Reach for the Promise constructor when something asynchronous does not already speak promises: callback-style APIs (fs.readFile, image.onload, geolocation), event-based flows where you want to await a single occurrence (a WebSocket's next open event, a user's confirmation click), or timers — the delay(ms) helper is the canonical example. The executor function you pass runs immediately and synchronously; you start the underlying operation inside it and call resolve(value) or reject(error) exactly once when the outcome is known. It is also the tool for building small synchronization primitives — deferreds, semaphores, once-only initialization gates — where you need to hand the resolve function to code living somewhere else. Equally important is knowing when not to use it: if you already have a promise (from fetch, an async function, or a library), wrapping it in new Promise() is the explicit-construction antipattern — it adds allocation, obscures intent, and in careless variants silently drops rejections. Compose with then()/await instead, use Promise.resolve() or Promise.reject() for pre-settled values, and prefer util.promisify in Node.js for standard error-first callbacks. In a modern codebase, hand-written constructor wrappers should be rare and live at the boundary between legacy APIs and your async/await code.
⚠️ Common Mistakes
Wrapping an existing promise: new Promise((res, rej) => fetch(url).then(res, rej)) — the explicit-construction antipattern. It duplicates machinery fetch already provides, and common variants that forget the second argument lose rejections entirely. If a promise already exists, chain it or await it.
Code paths that never call resolve() or reject() — for example an if/else where one branch settles and the other just returns. The promise stays pending forever, every await on it hangs silently with no error, and the pending promise plus all its attached handlers can never be garbage collected while referenced: a slow leak that is miserable to debug. Audit every exit path of the executor.
Assuming any error inside the executor rejects the promise. Only synchronous throws are converted automatically; if a callback inside setTimeout or an event handler throws, the error escapes to the global handler and the promise never settles. Inside asynchronous callbacks, wrap risky work in try/catch and call reject(err) explicitly.
Believing resolve() stops execution. It only settles the promise — statements after it still run, including a later reject() (which is silently ignored) or side effects you meant to skip. Write return resolve(value), or restructure so that settling is the executor's final act.
Doing heavy synchronous work in the executor and expecting it to be deferred. The executor runs inline during construction, before new Promise() even returns, so a blocking loop there freezes the caller — only the handlers attached with then() are asynchronous.
✅ Best Practices
Rely on the built-in safety net for synchronous failures — a throw inside the executor rejects the promise automatically, so no try/catch is needed at the top level — but be explicit with reject(new Error(...)) inside any nested asynchronous callback.
Settle exactly once and make it obvious: resolve or reject on every path, ideally as the final statement of that path. Subsequent calls are silently ignored, which can mask logic errors when two code paths race to settle.
Keep executors minimal: start the operation, translate its completion into resolve or reject, and nothing else. Business logic belongs in the surrounding async function, where errors, types, and control flow are far easier to manage.
Prefer util.promisify in Node.js, or a small once-style event helper, over hand-rolled wrappers for standard callback patterns; reserve the raw constructor for genuinely custom bridging such as deferreds and multi-event coordination.
Always reject with Error instances rather than bare strings, so consumers get stack traces, instanceof checks, and a proper cause chain when your wrapper translates low-level failures into domain errors.
⚡ Performance Notes
Constructing a promise is cheap — one object with internal slots — and the executor runs synchronously on the spot, so construction itself adds no latency and no queue traffic. Asynchrony begins at settlement: resolve() and reject() never invoke handlers directly; they enqueue the registered reactions as microtasks, which run after the current call stack unwinds and before any macrotask (timers, I/O callbacks) or rendering. Calling resolve with another promise or thenable adds one or two extra microtask ticks while the value is unwrapped — usually irrelevant, occasionally visible in ordering-sensitive tests. The costs worth engineering around are structural: forever-pending promises pin their reactions and closures in memory for as long as they are referenced, and executor code blocks the caller because it is synchronous. In hot paths that construct thousands of promises per second — per-row wrappers in a data pipeline, for instance — prefer batching, or promisify the function once and reuse it, rather than building wrappers inside the loop.
🌍 Real World Example
Promisified setTimeout
Two canonical bridges from callback-land into async/await. The delay() helper wraps setTimeout — the executor starts the timer and hands resolve over as its callback — enabling readable pauses in animation sequences, polling loops, and retry backoff. readFileAsync() wraps Node's error-first callback convention: reject on error, resolve with the data, exactly one settlement per path. Both wrappers are one-liners that live at the boundary; beyond this point every consumer uses clean await syntax, and neither wrapper contains business logic. That is precisely — and exclusively — the job the Promise constructor is meant to do.
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function animateSequence() {
console.log('Step 1: Fade in');
await delay(1000);
console.log('Step 2: Wait');
await delay(500);
console.log('Step 3: Fade out');
await delay(1000);
console.log('Animation complete!');
}
// Promisify callback-based API
function readFileAsync(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, 'utf8', (error, data) => {
if (error) reject(error);
else resolve(data);
});
});
}
animateSequence();