この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは、CX事業本部の夏目です。
先日、Javascriptの非同期処理に登場するPromiseとasync/awaitをJS初心者に説明する機会がありました。
なので、今回は説明したことを再度噛み砕いてから共有しようと思います。
Promiseの使い方
基本的な使い方
main.js
function sleep(sec) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(`wait: ${sec} sec`);
resolve(sec);
}, sec * 1000);
});
}
function main() {
sleep(2).then((data) => {
return sleep(data * 2);
}).then((data) => {
console.log(`sleep result: ${data}`)
return new Promise((resolve, reject) => {
resolve();
});
}).then((data) => {
console.log(`empty call: ${data}`);
return new Promise((resolve, reject) => {
reject("failed");
});
}).then((data) => {
console.log("not call 1");
}).catch((e) => {
console.log(`catch: ${e}`);
return new Promise((resolve) => {
resolve();
});
}).then((data) => {
console.log(`empty call take2: ${data}`);
return new Promise(() => {
throw new Error("throwed");
});
}).catch((e) => {
console.log(`failed: ${e}`);
return new Promise((resolve) => {
resolve()
});
}).catch((e) => {
console.log("not call 2")
}).finally(() => {
console.log("finally");
});
}
main();
実行結果
$ node main.js
wait: 2 sec
wait: 4 sec
sleep result: 4
empty call: undefined
catch: failed
empty call take2: undefined
failed: Error: throwed
finally
非同期処理を行いたい場合、Promiseのコンストラクタに渡す関数の中に記述します。
Promiseのコンストラクタに渡す関数は2つの引数を取ります。
第一引数resolve
, 第二引数reject
はどちらも1個もしくは0個の引数をとる関数です。
resolve
は非同期処理の成功時、reject
は失敗時に呼び出します。
注意すべきことは、これらはあくまで関数の呼び出しでしかないことです。
呼び出したからと言って、Promiseのコンストラクタに渡した関数が停止するわけではありません。
作成したPromiseオブジェクトに対して、基本的(?)に次の3つの関数を呼び出します。
また、これらの関数でメソッドチェーンを作って使います。
then((data) => {})
: Promiseオブジェクトのコンストラクタに渡した関数でresolve
が呼ばれたら、引数に渡した関数が実行されるcatch((e) => {})
: Promiseオブジェクトのコンストラクタに渡した関数でreject
が呼ばれたら、引数に渡した関数が実行されるfinally(() => {})
: メソッドチェーンで最後にかならず実行される
then()
やcatch()
は渡した関数でPromiseオブジェクトを渡せば、どんどんメソッドチェーンを伸ばすことができます。
複数の(非同期)処理を行うときなどチェーンを伸ばします。
catch()
はそれよりも前の部分で起きたエラーを拾います。
これはreject
が呼ばれたものだけではなく、throw
されたエラーであっても拾います。
Promise.all()
main.js
function sleep(sec) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(`wait: ${sec} sec`);
resolve(sec);
}, sec * 1000);
});
}
function main() {
Promise.all([
sleep(3),
sleep(1),
sleep(5),
sleep(4),
sleep(2)
]).then((result) => {
console.log(result);
});
}
main();
実行結果
wait: 1 sec
wait: 2 sec
wait: 3 sec
wait: 4 sec
wait: 5 sec
[ 3, 1, 5, 4, 2 ]
Promise.all()
という関数を使うと複数の処理が終わるのを待つことができます。
then()
に渡される値はPromise.all()
に渡した順番と一致します。
async/awaitの使い方
main.js
function sleep(sec) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`wait: ${sec} sec`);
resolve(sec);
}, sec * 1000);
});
}
function raise() {
return new Promise((_, reject) => {
reject(new Error("failed"));
});
}
async function sum(a, b) {
return a + b;
}
async function main() {
const sec = await sleep(2);
console.log(`result: ${sec} sec`);
try {
await raise();
} catch (e) {
console.log(`error: ${e}`);
}
const not_await = sum(1, 2);
console.log(`not await: ${not_await}`);
const did_await = await sum(2, 3);
console.log(`did await: ${did_await}`);
}
main()
実行結果
$ node main.js
wait: 2 sec
result: 2 sec
error: Error: failed
not await: [object Promise]
did await: 5
async/awaitというのはPromiseをもっと便利に扱うための機構です。
asyncと修飾した関数内でPromiseオブジェクトにawaitをつけることで、コンストラクタに渡した処理が完了するまで待ちます。
resolve
に渡した内容が返り値に、reject
に渡した内容はthrowされます。
また、asyncと修飾した関数はreturnで値を返していてもPromiseオブジェクトでラップされます。
そのため、awaitをつけることでreturnされた値を取得することができます。
まとめ
以上、Promiseとasync/awaitの使い方でした。
よく使うけどわかりにくい機能なので、理解の一助になれば幸いです。
雑に説明しただけなので、詳しく知りたいときは次のドキュメントなどで確認してください。
おまけ) 今は昔、Callback地獄といふものありけり
main.js
function sleep(sec, callback) {
setTimeout(function() {
console.log("wait: " + sec + " sec");
callback(sec);
}, sec * 1000);
}
function main() {
var sec0 = 1;
sleep(sec0, function(sec1) {
sleep(sec0 + sec1, function(sec2) {
sleep(sec1 + sec2, function(sec3) {
sleep(sec2 + sec3, function(sec4) {
sleep(sec3 + sec4, function(sec5) {
console.log("last wait time: " + sec5 + "sec");
});
});
});
});
});
}
main();
実行結果
wait: 1 sec
wait: 2 sec
wait: 3 sec
wait: 5 sec
wait: 8 sec
last wait time: 8sec