JavaScriptのPromiseとは?new Promise・resolve・rejectの使い方とthenのreturnを実行結果で解説

JavaScriptのPromiseは、「あとで結果が決まる処理」を表すオブジェクトです。new Promise((resolve, reject) => { … }) で作り、成功したらresolve(値)、失敗したらreject(エラー)を呼び、結果は.then()と.catch()で受け取ります。

この記事は、Promiseそのものの使い方に絞って解説します。new Promiseの書き方、resolveとrejectの動き、thenの中でreturnしたものがどう扱われるか、Promise.resolve・Promise.reject・Promise.raceなどの静的メソッドを、実際に動かした結果つきで説明します。コードはNode.js v24.21.0で実行しました。仕様の説明はMDNのPromiseのリファレンスに沿っています(2026年10月15日確認)。

async/awaitとの違いや使い分け、awaitのよくある落とし穴は、別記事のPromiseとasync/awaitの違いと使い分けで扱っています。async/awaitもPromiseの上に作られた書き方なので、先にこの記事でPromiseの動きをつかんでおくと理解しやすくなります。

Promiseとは:3つの状態

Promiseは、作られた時点では結果がまだ決まっていない処理の「引換券」のようなものです。状態は次の3つのどれかです。

状態 意味 どうなると移るか
pending(待機) 結果がまだ決まっていない 作った直後の状態
fulfilled(履行) 成功して値が決まった resolve(値) が呼ばれた
rejected(拒否) 失敗して理由(エラー)が決まった reject(理由) が呼ばれた、または例外が投げられた

一度fulfilledかrejectedになったPromiseの状態は、それ以上変わりません。この「決着済み」の状態をsettledと呼びます。

new Promiseの書き方:resolveとreject

Promiseを自分で作るときは、Promise()コンストラクターに関数(executor)を渡します。executorは引数として resolve と reject の2つの関数を受け取ります。

// 0.5秒後に、numが偶数なら成功・奇数なら失敗するPromiseを返す
function checkEven(num) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (num % 2 === 0) {
        resolve(`${num}は偶数です`);
      } else {
        reject(new Error(`${num}は奇数です`));
      }
    }, 500);
  });
}

checkEven(4)
  .then((message) => console.log("成功:", message))
  .catch((err) => console.log("失敗:", err.message));

checkEven(7)
  .then((message) => console.log("成功:", message))
  .catch((err) => console.log("失敗:", err.message));

実行結果:

成功: 4は偶数です
失敗: 7は奇数です

4は偶数なので resolve が呼ばれて .then() に、7は奇数なので reject が呼ばれて .catch() に進みました。reject には文字列ではなく new Error(...) を渡すのが一般的です。エラーの種類やスタックトレースが残るので、原因を追いやすくなります。

executorは同期的に動き、resolveは最初の1回だけ有効

new Promiseの動きで誤解されやすい点を、実行順で確かめます。

console.log("1. Promiseを作る前");

const p = new Promise((resolve, reject) => {
  console.log("2. executorはすぐ(同期的に)実行される");
  resolve("最初の結果");
  resolve("2回目の結果");      // 無視される
  reject(new Error("失敗"));  // これも無視される
  console.log("3. resolveの後も関数の残りは実行される");
});

p.then((value) => console.log("5. then:", value));
console.log("4. thenを登録した直後");

実行結果:

1. Promiseを作る前
2. executorはすぐ(同期的に)実行される
3. resolveの後も関数の残りは実行される
4. thenを登録した直後
5. then: 最初の結果

結果から次のことが分かります。

  • executor(new Promiseに渡した関数)は、Promiseを作った瞬間に同期的に実行される
  • resolveやrejectは最初に呼ばれた1回だけが有効で、2回目以降は無視される
  • resolveを呼んでも関数はそこで終わらず、残りの行も実行される。止めたいときは return resolve(...) のように書く
  • thenのコールバックは、すでに解決済みでも同期的には呼ばれず、今の処理が終わった後に実行される(4より5が後)

thenの中でreturnしたものはどうなるか

「return promise」で調べる人が多いのは、ここが分かりにくいからです。then()は常に新しいPromiseを返し、その結果はコールバックの中で何をしたかで決まります。

thenのコールバックの中で then()が返すPromiseの結果
値をreturnする その値で成功する
Promiseをreturnする そのPromiseが決着するまで待ち、同じ結果になる
例外をthrowする その例外で失敗する(次のcatchへ)
何もreturnしない undefinedで成功する
const wait = (ms, value) =>
  new Promise((resolve) => setTimeout(() => resolve(value), ms));

Promise.resolve(1)
  .then((n) => {
    console.log("A:", n);
    return n + 1;               // 値を返す → 次のthenにそのまま渡る
  })
  .then((n) => {
    console.log("B:", n);
    return wait(300, n * 10);   // Promiseを返す → 解決するまで待ってから次へ
  })
  .then((n) => {
    console.log("C:", n);
    throw new Error("Cで失敗"); // 例外を投げる → 次のcatchへ飛ぶ
  })
  .then(() => {
    console.log("D: ここは実行されない");
  })
  .catch((err) => {
    console.log("catch:", err.message);
    return "回復した値";        // catchで値を返すと、チェーンは成功に戻る
  })
  .then((v) => console.log("E:", v))
  .finally(() => console.log("finally: 成功でも失敗でも最後に実行"));

実行結果:

A: 1
B: 2
C: 20
catch: Cで失敗
E: 回復した値
finally: 成功でも失敗でも最後に実行

Bでは300ミリ秒待つPromiseをreturnしたので、Cはその結果(20)を受け取りました。Cで例外を投げるとDは飛ばされ、catchが受け取ります。catchの中で値を返すとチェーンは成功に戻り、Eが実行されます。finally() は結果にかかわらず最後に実行され、後片付けに使います。

関数からPromiseをreturnし忘れるとどうなるか

非同期の処理を含む関数は、作ったPromiseを呼び出し側に返さないと、呼び出し側は完了を待てません。

// returnを忘れた例:呼び出し側は完了を待てない
function saveBad(data) {
  new Promise((resolve) => setTimeout(() => {
    console.log("保存完了(bad):", data);
    resolve();
  }, 200));
}

// Promiseをreturnする例
function saveGood(data) {
  return new Promise((resolve) => setTimeout(() => {
    console.log("保存完了(good):", data);
    resolve();
  }, 200));
}

console.log("saveBadの戻り値:", saveBad("A"));
saveGood("B").then(() => console.log("Bの保存を待ってから次の処理"));

実行結果:

saveBadの戻り値: undefined
保存完了(bad): A
保存完了(good): B
Bの保存を待ってから次の処理

saveBad は内部でPromiseを作っていますが返していないので、戻り値は undefined です。呼び出し側は .then() をつなげられず、保存が終わる前に次の処理へ進んでしまいます。thenの中で別の非同期処理を呼ぶときも同じで、return を付け忘れると、チェーンがその処理を待たずに先へ進みます。

Promise.resolveとPromise.reject

Promise.resolve()は値を成功済みのPromiseに包み、Promise.reject()は失敗済みのPromiseを作ります。

// Promise.resolve:値をPromiseに包む。Promiseを渡すとそのまま返す
const p = Promise.resolve(42);
console.log(p instanceof Promise, Promise.resolve(p) === p);
p.then((v) => console.log("resolve:", v));

// Promise.reject:最初から失敗しているPromiseを作る
function findUser(id) {
  if (typeof id !== "number") {
    return Promise.reject(new TypeError("idは数値で指定してください"));
  }
  return Promise.resolve({ id, name: "user" + id });
}

findUser(3).then((u) => console.log("found:", u.name));
findUser("3").catch((e) => console.log(e.name + ":", e.message));

実行結果:

true true
resolve: 42
found: user3
TypeError: idは数値で指定してください

Promise.resolve(p) === p が true になっているように、すでにPromiseであるものを渡すと同じPromiseがそのまま返ります。関数の入口で引数をチェックして、おかしければ Promise.reject を返すようにすると、その関数は「常にPromiseを返す」ことになり、呼び出し側は失敗を .catch() だけで扱えます。

Promise.raceとPromise.any

複数のPromiseのうち「最初に決まったもの」を使うメソッドが2つあります。全部の完了を待つ Promise.all と Promise.allSettled は、使い分けをasync/awaitの記事の一覧表にまとめています。

メソッド 結果になるもの 全部失敗した場合
Promise.race() 最初に決着したもの(成功でも失敗でも) 最初に失敗したものの理由で失敗
Promise.any() 最初に成功したもの AggregateErrorで失敗
const wait = (ms, value) =>
  new Promise((resolve) => setTimeout(() => resolve(value), ms));
const timeout = (ms) =>
  new Promise((_, reject) =>
    setTimeout(() => reject(new Error(`${ms}msでタイムアウト`)), ms));

// Promise.race:最初に決着した(成功でも失敗でも)ものの結果になる
Promise.race([wait(100, "100msの処理"), timeout(500)])
  .then((v) => console.log("race 1:", v))
  .catch((e) => console.log("race 1:", e.message));

Promise.race([wait(800, "800msの処理"), timeout(500)])
  .then((v) => console.log("race 2:", v))
  .catch((e) => console.log("race 2:", e.message));

// Promise.any:最初に「成功」したものの結果になる(全部失敗ならAggregateError)
Promise.any([timeout(50), wait(300, "300msで成功")])
  .then((v) => console.log("any:", v));

Promise.any([timeout(10), timeout(20)])
  .catch((e) => console.log("any 全滅:", e.constructor.name, e.errors.length + "件"));

実行結果:

any 全滅: AggregateError 2件
race 1: 100msの処理
any: 300msで成功
race 2: 500msでタイムアウト

出力は、決着した時刻の順に並んでいます。race 1は100ミリ秒の処理が先に終わったので成功、race 2は500ミリ秒のタイムアウトが先に失敗したので失敗になりました。Promise.any は50ミリ秒で失敗したものを無視して、300ミリ秒で成功したものを採用しています。

注意点として、Promise.race でタイムアウトにしても、負けた側の処理(race 2の800ミリ秒の処理)は止まらずに最後まで動きます。通信そのものを止めたい場合は、AbortSignal.timeout() などで処理側をキャンセルする必要があります。

新しめの静的メソッド:withResolversとtry

MDNによると、Promise.withResolvers()は2024年3月以降すべてのブラウザーで利用可能、Promise.try()は「Baseline 2025」で、2025年1月以降の最新のブラウザーで動作し、古い端末やブラウザーでは動作しないことがあるとされています(2026年10月15日確認)。

// Promise.withResolvers:resolve/rejectを外に取り出す
const { promise, resolve } = Promise.withResolvers();
setTimeout(() => resolve("ボタンが押された"), 200);
promise.then((v) => console.log("withResolvers:", v));

// Promise.try:同期の例外も非同期の失敗も、同じcatchで受け取る
function parseConfig(text) {
  return JSON.parse(text); // 不正なJSONなら同期的に例外を投げる
}
Promise.try(parseConfig, '{"debug": true}')
  .then((cfg) => console.log("try 成功:", cfg.debug));
Promise.try(parseConfig, "{debug}")
  .catch((e) => console.log("try 失敗:", e.name));

実行結果:

try 成功: true
try 失敗: SyntaxError
withResolvers: ボタンが押された
  • withResolvers:executorの外でresolve・rejectを呼びたいとき(イベントが来たら解決する、など)に、let resolve; を外で宣言する書き方の代わりに使えます
  • try:渡した関数が同期的に例外を投げても、Promiseの失敗として .catch() で受け取れます。上の例では不正なJSONによる SyntaxError がcatchに届いています

コールバック形式の関数をPromiseにする

古いAPIには、結果をコールバック関数で受け取る形式のものがあります。new Promiseで包めば、then・catchやawaitで扱えるようになります。Node.jsには同じことをする util.promisify もあり、ファイル操作には最初からPromiseを返す fs.promises もあります。

const fs = require("node:fs");
const { promisify } = require("node:util");

fs.writeFileSync("memo.txt", "Promise化のテスト");

// コールバック形式の関数を、自分でPromiseに包む
function readText(path) {
  return new Promise((resolve, reject) => {
    fs.readFile(path, "utf8", (err, data) => {
      if (err) reject(err);
      else resolve(data);
    });
  });
}
readText("memo.txt").then((t) => console.log("自作:", t));

// Node.js標準のutil.promisifyでも同じことができる
const readFileP = promisify(fs.readFile);
readFileP("memo.txt", "utf8").then((t) => console.log("promisify:", t));

// 最初からPromiseを返すAPIもある
fs.promises.readFile("no-such-file.txt", "utf8")
  .catch((e) => console.log("fs.promises:", e.code));

実行結果:

fs.promises: ENOENT
自作: Promise化のテスト
promisify: Promise化のテスト

自分でnew Promiseを書くのは、このように「Promiseを返さない古いAPIを包むとき」が中心です。すでにPromiseを返す関数を、さらにnew Promiseで包む必要はありません。そのまま return すれば十分です。

Promiseでつまずきやすい点のまとめ

  • catchを付けない:失敗したPromiseを誰も受け取らないと、Node.jsでは未処理の拒否として扱われる。チェーンの最後には .catch() を付ける
  • thenの中でreturnを忘れる:次のthenが待ってくれない
  • thenの中でthenを入れ子にする:内側のPromiseをreturnすれば、チェーンを平らに書ける
  • rejectに文字列を渡す:new Error() を渡すと原因を追いやすい
  • raceで負けた処理が止まると思い込む:止めたいなら処理側をキャンセルする

JavaScriptそのものを学び始めたばかりの方は、JavaScript入門ガイドで学習の順序と非同期処理のつまずき方を先に確認しておくと、この記事の内容がつながりやすくなります。

よくある質問

Q. JavaScriptのPromiseとは何ですか?

結果があとで決まる非同期処理を表すオブジェクトです。pending(待機)、fulfilled(成功)、rejected(失敗)の3つの状態を持ち、結果は.then()と.catch()で受け取ります。async/awaitもPromiseの上に作られた書き方です。

Q. resolveとrejectは何をする関数ですか?

new Promiseに渡す関数(executor)が受け取る2つの関数です。resolve(値)を呼ぶとPromiseはその値で成功し、reject(理由)を呼ぶと失敗します。最初に呼ばれた1回だけが有効で、2回目以降は無視されます。

Q. thenの中でPromiseをreturnするとどうなりますか?

then()が返すPromiseは、returnしたPromiseが決着するまで待ち、同じ結果になります。値を返せばその値で成功、例外を投げれば失敗します。returnを忘れると、次のthenはその処理を待たずに進みます。

Q. Promise.raceとPromise.anyの違いは?

Promise.raceは成功・失敗にかかわらず最初に決着したものの結果になります。Promise.anyは最初に成功したものの結果になり、全部失敗した場合だけAggregateErrorで失敗します。

Q. Promise.rejectはどんなときに使いますか?

最初から失敗しているPromiseを返したいときに使います。たとえば非同期関数の入口で引数が不正だったとき、例外を投げる代わりにPromise.rejectを返すと、呼び出し側は失敗を.catch()だけで扱えます。

言語やフレームワークの違いは似たプログラミング言語の違いで比較しています。

プログラミング学習に興味がある方は、まずは無料体験会へ

無料体験会・お申し込みはこちら