Promiseチェーン

概要: このチュートリアルでは、非同期操作を順番に実行するためにPromiseをチェーンするJavaScriptのPromiseチェーンパターンについて学習します。

JavaScript Promiseチェーンの紹介

前の操作の結果から次の操作を開始する、2つ以上の関連する非同期操作を実行したい場合があります。例えば

まず、3秒後に数値10に解決される新しいPromiseを作成します。

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(10);
    }, 3 * 100);
});Code language: JavaScript (javascript)

setTimeout()関数は非同期操作をシミュレートすることに注意してください。

次に、Promiseのthen()メソッドを呼び出します。

p.then((result) => {
    console.log(result);
    return result * 2;
});Code language: JavaScript (javascript)

then()メソッドに渡されたコールバックは、Promiseが解決されると実行されます。コールバックでは、Promiseの結果を表示し、2倍(result*2)した新しい値を返します。

then()メソッドは、値に解決された値を持つ新しいPromiseを返すため、返されたPromiseに対してthen()メソッドを次のように呼び出すことができます。

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(10);
    }, 3 * 100);
});

p.then((result) => {
    console.log(result);
    return result * 2;
}).then((result) => {
    console.log(result);
    return result * 3;
});Code language: JavaScript (javascript)

出力

10
20

この例では、最初のthen()メソッドの戻り値が2番目のthen()メソッドに渡されます。次のように、then()メソッドを連続して呼び出すことができます。

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(10);
    }, 3 * 100);
});

p.then((result) => {
    console.log(result); // 10
    return result * 2;
}).then((result) => {
    console.log(result); // 20
    return result * 3;
}).then((result) => {
    console.log(result); // 60
    return result * 4;
});
Code language: JavaScript (javascript)

出力

10
20
60

このようにthen()メソッドを呼び出す方法は、しばしばPromiseチェーンと呼ばれます。

次の図はPromiseチェーンを示しています。

JavaScript Promise Chaining

Promiseの複数のハンドラー

1つのPromiseに対してthen()メソッドを複数回呼び出す場合、それはPromiseチェーンではありません。例えば

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(10);
    }, 3 * 100);
});

p.then((result) => {
    console.log(result); // 10
    return result * 2;
})

p.then((result) => {
    console.log(result); // 10
    return result * 3;
})

p.then((result) => {
    console.log(result); // 10
    return result * 4;
});Code language: JavaScript (javascript)

出力

10
10
10

この例では、1つのPromiseに対して複数のハンドラーがあります。これらのハンドラーには関係がありません。また、それらは独立して実行され、上記のPromiseチェーンのように結果を互いに渡しません。

次の図は、複数のハンドラーを持つPromiseを示しています。

JavaScript Promise Chaining - multiple handlers

実際には、1つのPromiseに対して複数のハンドラーを使用することはほとんどありません。

Promiseを返す

then()メソッドで値を返すと、then()メソッドは戻り値にすぐに解決される新しいPromiseを返します。

また、then()メソッドで新しいPromiseを返すこともできます。例えば

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(10);
    }, 3 * 100);
});

p.then((result) => {
    console.log(result);
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(result * 2);
        }, 3 * 1000);
    });
}).then((result) => {
    console.log(result);
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(result * 3);
        }, 3 * 1000);
    });
}).then(result => console.log(result));
Code language: JavaScript (javascript)

出力

10
20
60

この例では、3秒ごとに10、20、60が表示されます。このコードパターンにより、いくつかのタスクを順番に実行できます。

上記の例を修正しました

function generateNumber(num) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(num);
    }, 3 * 1000);
  });
}

generateNumber(10)
  .then((result) => {
    console.log(result);
    return generateNumber(result * 2);
  })
  .then((result) => {
    console.log(result);
    return generateNumber(result * 3);
  })
  .then((result) => console.log(result));
Code language: JavaScript (javascript)

Promiseチェーンの構文

複数の非同期タスクを順番に実行したい場合があります。さらに、前のステップの結果を次のステップに渡す必要があります。この場合、次の構文を使用できます。

step1()
    .then(result => step2(result))
    .then(result => step3(result))
    ...
Code language: JavaScript (javascript)

結果を渡せずに前のタスクの結果を次のタスクに渡す必要がある場合は、この構文を使用します。

step1()
    .then(step2)
    .then(step3)
    ...
Code language: CSS (css)

次の非同期操作を順番に実行したいとします。

  • まず、データベースからユーザーを取得します。
  • 次に、選択したユーザーのサービスを取得します。
  • 3番目に、ユーザーのサービスからサービスコストを計算します。

次の関数は、3つの非同期操作を示しています。

function getUser(userId) {
    return new Promise((resolve, reject) => {
        console.log('Get the user from the database.');
        setTimeout(() => {
            resolve({
                userId: userId,
                username: 'admin'
            });
        }, 1000);
    })
}

function getServices(user) {
    return new Promise((resolve, reject) => {
        console.log(`Get the services of ${user.username} from the API.`);
        setTimeout(() => {
            resolve(['Email', 'VPN', 'CDN']);
        }, 3 * 1000);
    });
}

function getServiceCost(services) {
    return new Promise((resolve, reject) => {
        console.log(`Calculate the service cost of ${services}.`);
        setTimeout(() => {
            resolve(services.length * 100);
        }, 2 * 1000);
    });
}Code language: JavaScript (javascript)

以下は、シーケンスをシリアル化するためにPromiseを使用しています。

getUser(100)
    .then(getServices)
    .then(getServiceCost)
    .then(console.log);Code language: CSS (css)

出力

Get the user from the database.
Get the services of admin from the API.
Calculate the service cost of Email,VPN,CDN.
300Code language: JavaScript (javascript)

ES2017では、Promiseチェーン技術を使用するよりもクリーンなコードを書くのに役立つasync/awaitが導入されたことに注意してください。

このチュートリアルでは、複数の非同期タスクを順番に実行するPromiseチェーンについて学習しました。

クイズ

このチュートリアルは役に立ちましたか?