JavaScript Promise完全ガイド!非同期処理とasync/awaitの使い方【2025年最新版】

 

JavaScriptで非同期処理を学習していて、Promiseの仕組みや使い方が分からないと悩んでいませんか?Promiseは、コールバック地獄を解決し、より読みやすく保守しやすい非同期コードを書くための重要な仕組みです。この記事では、Promise初心者でも理解できるように、基本的な概念から実践的な使い方まで、分かりやすく解説します。

JavaScript Promiseとは?非同期処理の救世主

Promiseは、JavaScriptで非同期処理を扱うためのオブジェクトです。「将来的に値が返される約束(Promise)」を表現し、処理の成功・失敗を適切に処理できます。

Promiseが解決する問題

  • コールバック地獄: 複数の非同期処理のネストを解消
  • エラーハンドリング: 統一的なエラー処理が可能
  • 可読性向上: より直感的で読みやすいコード
  • 処理の連鎖: .then()による処理の連結

Promiseの状態

状態説明
Pending処理中(初期状態)
Fulfilled処理成功(resolved)
Rejected処理失敗

【基本編】Promiseの基本的な使い方

1. Promiseの作成

// 基本的なPromiseの作成
const myPromise = new Promise((resolve, reject) => {
    const success = true;
    
    if (success) {
        resolve("処理が成功しました");
    } else {
        reject("処理が失敗しました");
    }
});

// Promiseの使用
myPromise
    .then(result => console.log(result))
    .catch(error => console.error(error));

2. 非同期処理のシミュレーション

function delay(ms) {
    return new Promise(resolve => {
        setTimeout(() => resolve("完了"), ms);
    });
}

delay(2000)
    .then(result => {
        console.log(result); // 2秒後に"完了"が出力
    });

3. thenとcatchの基本

function fetchUserData(userId) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (userId > 0) {
                resolve({ id: userId, name: "ユーザー" + userId });
            } else {
                reject(new Error("無効なユーザーID"));
            }
        }, 1000);
    });
}

fetchUserData(1)
    .then(user => console.log("ユーザー:", user))
    .catch(error => console.error("エラー:", error.message));

【チェーン編】Promiseチェーンと処理の連結

1. 基本的なPromiseチェーン

function step1() {
    return Promise.resolve("ステップ1完了");
}

function step2(data) {
    return Promise.resolve(data + " → ステップ2完了");
}

function step3(data) {
    return Promise.resolve(data + " → ステップ3完了");
}

step1()
    .then(result => step2(result))
    .then(result => step3(result))
    .then(finalResult => console.log(finalResult))
    .catch(error => console.error(error));

2. データ変換のチェーン

function fetchData() {
    return Promise.resolve('{"name": "田中太郎", "age": 30}');
}

fetchData()
    .then(jsonString => JSON.parse(jsonString))
    .then(userData => ({ ...userData, status: "active" }))
    .then(enrichedData => {
        console.log("処理結果:", enrichedData);
        return enrichedData;
    })
    .catch(error => console.error("処理エラー:", error));

3. 条件付きチェーン

function authenticate(token) {
    return new Promise((resolve, reject) => {
        if (token === "valid-token") {
            resolve({ authenticated: true, userId: 123 });
        } else {
            reject(new Error("認証失敗"));
        }
    });
}

function fetchUserProfile(userId) {
    return Promise.resolve({ name: "ユーザー", email: "user@example.com" });
}

const token = "valid-token";

authenticate(token)
    .then(authResult => {
        if (authResult.authenticated) {
            return fetchUserProfile(authResult.userId);
        }
        throw new Error("認証されていません");
    })
    .then(profile => console.log("プロフィール:", profile))
    .catch(error => console.error(error.message));

【並列処理編】Promise.all・Promise.race・Promise.allSettled

1. Promise.all(全て成功時のみ)

const promise1 = Promise.resolve("データ1");
const promise2 = Promise.resolve("データ2");
const promise3 = Promise.resolve("データ3");

Promise.all([promise1, promise2, promise3])
    .then(results => {
        console.log("全ての処理が完了:", results);
        // ["データ1", "データ2", "データ3"]
    })
    .catch(error => {
        console.error("いずれかの処理が失敗:", error);
    });

2. Promise.race(最初に完了したもの)

const fast = new Promise(resolve => setTimeout(() => resolve("高速"), 100));
const slow = new Promise(resolve => setTimeout(() => resolve("低速"), 2000));

Promise.race([fast, slow])
    .then(result => {
        console.log("最初に完了:", result); // "高速"
    });

3. Promise.allSettled(全て実行、結果は個別に判定)

const success = Promise.resolve("成功");
const failure = Promise.reject("失敗");

Promise.allSettled([success, failure])
    .then(results => {
        results.forEach((result, index) => {
            if (result.status === 'fulfilled') {
                console.log(`処理${index + 1} 成功:`, result.value);
            } else {
                console.log(`処理${index + 1} 失敗:`, result.reason);
            }
        });
    });

【async/await編】より読みやすい非同期処理

1. async/awaitの基本

// Promiseベース
function fetchDataPromise() {
    return fetch('/api/data')
        .then(response => response.json())
        .then(data => console.log(data))
        .catch(error => console.error(error));
}

// async/awaitベース
async function fetchDataAsync() {
    try {
        const response = await fetch('/api/data');
        const data = await response.json();
        console.log(data);
    } catch (error) {
        console.error(error);
    }
}

2. 複数の非同期処理

async function processMultipleData() {
    try {
        // 順次実行
        const user = await fetchUser(1);
        const posts = await fetchUserPosts(user.id);
        const comments = await fetchPostComments(posts[0].id);
        
        return { user, posts, comments };
    } catch (error) {
        console.error("データ取得エラー:", error);
        throw error;
    }
}

// 並列実行
async function processParallelData() {
    try {
        const [users, posts, categories] = await Promise.all([
            fetchAllUsers(),
            fetchAllPosts(),
            fetchCategories()
        ]);
        
        return { users, posts, categories };
    } catch (error) {
        console.error("並列処理エラー:", error);
        throw error;
    }
}

3. ループでのasync/await

// ❌ 悪い例:並列実行されない
async function processUsersBad(userIds) {
    const results = [];
    for (const id of userIds) {
        const user = await fetchUser(id); // 順次実行
        results.push(user);
    }
    return results;
}

// ✅ 良い例:並列実行
async function processUsersGood(userIds) {
    const promises = userIds.map(id => fetchUser(id));
    return Promise.all(promises);
}

【実践編】実際のWebアプリケーションでの使用例

1. API呼び出しのラッパー関数

class ApiClient {
    constructor(baseUrl) {
        this.baseUrl = baseUrl;
    }
    
    async request(endpoint, options = {}) {
        const url = `${this.baseUrl}${endpoint}`;
        
        try {
            const response = await fetch(url, {
                headers: { 'Content-Type': 'application/json' },
                ...options
            });
            
            if (!response.ok) {
                throw new Error(`HTTP Error: ${response.status}`);
            }
            
            return await response.json();
        } catch (error) {
            console.error('API Request failed:', error);
            throw error;
        }
    }
    
    getUser(id) {
        return this.request(`/users/${id}`);
    }
    
    createUser(userData) {
        return this.request('/users', {
            method: 'POST',
            body: JSON.stringify(userData)
        });
    }
}

// 使用例
const api = new ApiClient('https://api.example.com');
api.getUser(1).then(user => console.log(user));

2. データベース操作のシミュレーション

class Database {
    constructor() {
        this.data = new Map();
    }
    
    async save(key, value) {
        // 非同期処理のシミュレーション
        await new Promise(resolve => setTimeout(resolve, 100));
        this.data.set(key, value);
        return { success: true, key, value };
    }
    
    async find(key) {
        await new Promise(resolve => setTimeout(resolve, 50));
        const value = this.data.get(key);
        if (value === undefined) {
            throw new Error(`Key '${key}' not found`);
        }
        return value;
    }
    
    async delete(key) {
        await new Promise(resolve => setTimeout(resolve, 75));
        const deleted = this.data.delete(key);
        return { success: deleted, key };
    }
}

// 使用例
async function databaseExample() {
    const db = new Database();
    
    try {
        await db.save('user:1', { name: '田中', age: 30 });
        const user = await db.find('user:1');
        console.log('取得したユーザー:', user);
        
        await db.delete('user:1');
        console.log('ユーザーを削除しました');
    } catch (error) {
        console.error('データベースエラー:', error.message);
    }
}

3. ファイルアップロード処理

async function uploadFile(file, progressCallback) {
    return new Promise((resolve, reject) => {
        const formData = new FormData();
        formData.append('file', file);
        
        const xhr = new XMLHttpRequest();
        
        xhr.upload.onprogress = (event) => {
            if (event.lengthComputable) {
                const progress = (event.loaded / event.total) * 100;
                progressCallback(progress);
            }
        };
        
        xhr.onload = () => {
            if (xhr.status === 200) {
                resolve(JSON.parse(xhr.responseText));
            } else {
                reject(new Error(`Upload failed: ${xhr.status}`));
            }
        };
        
        xhr.onerror = () => reject(new Error('Network error'));
        
        xhr.open('POST', '/upload');
        xhr.send(formData);
    });
}

// 使用例
async function handleFileUpload(fileInput) {
    const file = fileInput.files[0];
    if (!file) return;
    
    try {
        const result = await uploadFile(file, (progress) => {
            console.log(`アップロード進捗: ${progress.toFixed(1)}%`);
        });
        
        console.log('アップロード完了:', result);
    } catch (error) {
        console.error('アップロードエラー:', error.message);
    }
}

【エラーハンドリング編】Promiseのエラー処理

1. 基本的なエラーハンドリング

async function robustApiCall(url) {
    try {
        const response = await fetch(url);
        
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }
        
        const data = await response.json();
        return data;
    } catch (error) {
        if (error instanceof TypeError) {
            throw new Error('ネットワークエラーが発生しました');
        } else if (error.message.includes('HTTP 404')) {
            throw new Error('リソースが見つかりません');
        } else {
            throw error; // その他のエラーはそのまま再投出
        }
    }
}

2. リトライ機能付きのPromise

async function retryPromise(promiseFactory, maxRetries = 3, delay = 1000) {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            return await promiseFactory();
        } catch (error) {
            console.log(`試行 ${attempt} 失敗:`, error.message);
            
            if (attempt === maxRetries) {
                throw new Error(`${maxRetries}回の試行後に失敗: ${error.message}`);
            }
            
            // 指数バックオフで待機
            await new Promise(resolve => setTimeout(resolve, delay * attempt));
        }
    }
}

// 使用例
async function unreliableApiCall() {
    // 70%の確率で失敗するAPI
    if (Math.random() < 0.7) {
        throw new Error('API呼び出し失敗');
    }
    return { success: true, data: 'APIレスポンス' };
}

retryPromise(() => unreliableApiCall(), 3, 500)
    .then(result => console.log('成功:', result))
    .catch(error => console.error('最終的に失敗:', error.message));

3. タイムアウト機能

function withTimeout(promise, timeoutMs) {
    const timeout = new Promise((_, reject) => {
        setTimeout(() => {
            reject(new Error(`タイムアウト: ${timeoutMs}ms`));
        }, timeoutMs);
    });
    
    return Promise.race([promise, timeout]);
}

// 使用例
async function slowApiCall() {
    await new Promise(resolve => setTimeout(resolve, 3000)); // 3秒かかる処理
    return 'API結果';
}

withTimeout(slowApiCall(), 2000) // 2秒でタイムアウト
    .then(result => console.log(result))
    .catch(error => console.error(error.message)); // "タイムアウト: 2000ms"

【パフォーマンス編】Promise最適化テクニック

1. 不要な await の回避

// ❌ 悪い例:不要な await
async function inefficientFunction() {
    const result1 = await operation1();
    const result2 = await operation2();
    return await combineResults(result1, result2);
}

// ✅ 良い例:最適化版
async function efficientFunction() {
    const [result1, result2] = await Promise.all([
        operation1(),
        operation2()
    ]);
    return combineResults(result1, result2); // 最後の return に await 不要
}

2. Promise コンストラクタの適切な使用

// ❌ 悪い例:Promise コンストラクタの乱用
function badPromiseUsage() {
    return new Promise((resolve) => {
        resolve(fetch('/api/data')); // fetch は既に Promise を返す
    });
}

// ✅ 良い例:既存の Promise をそのまま使用
function goodPromiseUsage() {
    return fetch('/api/data');
}

3. メモ化による重複処理の回避

class CachedApiClient {
    constructor() {
        this.cache = new Map();
    }
    
    async fetchWithCache(url, cacheTime = 60000) {
        const cacheKey = url;
        const cached = this.cache.get(cacheKey);
        
        if (cached && Date.now() - cached.timestamp < cacheTime) {
            return cached.data;
        }
        
        try {
            const response = await fetch(url);
            const data = await response.json();
            
            this.cache.set(cacheKey, {
                data,
                timestamp: Date.now()
            });
            
            return data;
        } catch (error) {
            console.error('Fetch error:', error);
            throw error;
        }
    }
}

【デバッグ編】Promise のデバッグテクニック

1. Promise のログ出力

function logPromise(promise, label) {
    console.log(`${label}: Promise 開始`);
    
    return promise
        .then(result => {
            console.log(`${label}: Promise 成功`, result);
            return result;
        })
        .catch(error => {
            console.error(`${label}: Promise 失敗`, error);
            throw error;
        });
}

// 使用例
logPromise(fetch('/api/data'), 'API Call')
    .then(response => response.json())
    .then(data => console.log('最終結果:', data));

2. 未処理の Promise 拒否の検出

// 未処理の Promise 拒否を検出
window.addEventListener('unhandledrejection', (event) => {
    console.error('未処理の Promise 拒否:', event.reason);
    event.preventDefault(); // デフォルトの動作を防ぐ
});

// 例:エラーハンドリングを忘れた Promise
fetch('/api/nonexistent').then(response => response.json());
// 上記は catch がないため unhandledrejection イベントが発生

3. Promise の状態確認

function createInspectablePromise(executor) {
    let state = 'pending';
    let value;
    let reason;
    
    const promise = new Promise((resolve, reject) => {
        const wrappedResolve = (val) => {
            state = 'fulfilled';
            value = val;
            resolve(val);
        };
        
        const wrappedReject = (err) => {
            state = 'rejected';
            reason = err;
            reject(err);
        };
        
        executor(wrappedResolve, wrappedReject);
    });
    
    promise.inspect = () => ({ state, value, reason });
    
    return promise;
}

// 使用例
const inspectablePromise = createInspectablePromise((resolve) => {
    setTimeout(() => resolve('完了'), 1000);
});

console.log(inspectablePromise.inspect()); // { state: 'pending', value: undefined, reason: undefined }

まとめ:Promise で非同期処理をマスターしよう

JavaScript の Promise は、非同期処理を効率的かつ読みやすく書くための重要な仕組みです。この記事で紹介した基本的な使い方から高度なテクニックまでを段階的に習得することで、より品質の高い非同期コードを書けるようになります。

重要なポイント

  • Promise チェーン: .then() を使った処理の連結で可読性向上
  • async/await: より同期的な書き方で直感的なコード
  • 並列処理: Promise.all()Promise.race() で効率的な処理
  • エラーハンドリング: 適切な try-catch.catch() でロバストなコード
  • パフォーマンス: 不要な await を避け、キャッシュを活用

まずは基本的な Promise の作成と .then()/.catch() から始めて、徐々に async/await や並列処理などの高度な機能を取り入れてみてください。Promise をマスターすることで、モダンな JavaScript 開発において必須のスキルを身につけることができます。


この記事がお役に立ちましたら、ぜひシェアしてください。JavaScript Promise や非同期処理に関するご質問がございましたら、お気軽にコメントでお知らせください。

■プロンプトだけでオリジナルアプリを開発・公開してみた!!

■AI時代の第一歩!「AI駆動開発コース」はじめました!

テックジム東京本校で先行開始。

■テックジム東京本校

「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。

<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。

<月1開催>放送作家による映像ディレクター養成講座

<オンライン無料>ゼロから始めるPython爆速講座