久々にNode.jsを書いたらPromise、async、awaitの使い方をすっかり忘れており思い出すのに結構時間がかかったためちゃんとアウトプットして忘れないようにします。
ついでにNode.jsからshellを同期的に実行する処理を作ったので、ユースケースとして載せときます。
目次
Promise, async, awaitとは
- Promise
-
Promiseオブジェクトは非同期処理の最終的な完了処理(もしくは失敗)およびその結果の値を表現します。
MDN web docs
ちょっと意味がわかりません。。。
正確な定義はよくわかんないですが、要は非同期処理を自在に操作するためのオブジェクトとその処理パターンだと理解してます。
例)- 順次処理
123promise.then((result)=>{ return promise2();}).then((result)=>{ return promise3();})
- 並列処理
12345678promise.all([promise1(),promise2(),promise3()]).then((result)=>{処理})
- エラーハンドリング
123promise.then((result)=>{ 処理 }).catch((e)=>{ 処理 })
- 順次処理
- async
-
async function宣言は、 AsyncFunctionオブジェクトを返す非同期関数を定義します。非同期関数は非同期でイベントループを介して実行され、暗黙的にPromiseを返します。
MDN web docs
例によって意味がわかりませんが、Promiseよりはまだ理解できそうな気がします。
async function ()
で非同期関数を宣言できる- 非同期関数は必ずPromiseを返す
- await
-
await演算子は、async functionによってPromiseが返されるのを待機するために使用します。
MDN web docs
async functionで定義された非同期関数からPromiseが返されるのを待ってくれる演算子です。これを使うと非同期関数の順次処理がsimpleに記述できる。
Promiseの使い方 ※all()とかrace()には触れません
Promiseを返す処理の作り方
なにはともあれPromiseは返す処理を作らないと話は始まりません。
関数内でnew Promise()
をreturnするというのが基本の型になります。
1 2 3 4 5 |
function myPromise() { return new Promise((resolve, reject) => { ここに処理を記述 }) } |
resolve()とreject()
- resolve()
- Promise処理の中でresolve(引数)を使うことで引数に指定された値をPromiseに変換して返す
- reject()
- resolveのエラー情報版
つまりresoleve()
はPromise処理の中で正常系の値をreturnする、
reject()
はPromise処理の中で例外をreturnすると覚えておけばOK。
1 2 3 4 5 6 7 8 9 10 11 |
function myPromise() { return new Promise((resolve, reject) => { // var err = new Error('エラーが発生しました!'); var err = ''; if (!err) { resolve('Hello Promise!'); }else { reject(err); } }) } |
Promiseの戻り値を操作する
正常処理
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function myPromise() { return new Promise((resolve, reject) => { // var err = new Error('エラーが発生しました!'); var err = ''; if (!err) { resolve('Hello Promise!'); }else { reject(err); } }) } myPromise() //myPromiseからの戻り値を .then((data)=>{console.log(data);}) //dataに渡して出力 |
【実行結果】
1 |
Hello Promise! |
直列に処理(メソッドチェーン)
myPromise → myPromise2の順で処理したい場合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function myPromise() { return new Promise((resolve, reject) => { // var err = new Error('エラーが発生しました!'); var err = ''; if (!err) { resolve('Hello Promise!'); }else { reject(err); } }) } function myPromise2() { return new Promise((resolve) => { resolve('Hello 2nd Promise!') }) } myPromise() //myPromiseからの戻り値を .then((data)=>{console.log(data);return myPromise2()}) //dataに渡して出力。その後myPromise2をreturn .then((data)=>{console.log(data)}) //returnされたmyPromise2の戻り値を出力 |
【実行結果】
1 2 |
Hello Promise! Hello 2nd Promise! |
エラーハンドリング
エラーの場合はreject()
で返したエラーをcatch()
で受け取る。(then()
では受け取れないので注意)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function myPromise() { return new Promise((resolve, reject) => { var err = new Error('エラーが発生しました!'); // var err = ''; if (!err) { resolve('Hello Promise!'); }else { reject(err); } }) } myPromise() //myPromiseのreject()からの戻り値を .catch((e)=>{console.log(e);}) //eに渡して出力 |
【実行結果】
1 2 |
Error: エラーが発生しました! [~以下省略~] |
asyncの使い方
Promiseと比較して考えるとわかりやすい。
asyncはawaitと組み合わせて初めて意味があると思う。
asyncの基本的な使い方
【Promiseで書いた場合】
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function myPromise() { return new Promise((resolve, reject) => { // var err = new Error('エラーが発生しました!'); var err = ''; if (!err) { resolve('Hello Promise!'); }else { reject(err); } }) } myPromise() .then((data)=>{console.log(data);}) |
【Asyncで書いた場合】
return new Promise
を書かずともPromiseを返す。
1 2 3 4 5 6 7 8 9 10 11 |
async function myAsync() { // var err = new Error('エラーが発生しました!'); var err = ''; if (!err) { return 'Hello Async!'; }else { return err; } } myAsync() .then((data)=>{console.log(data);}) |
Async関数の中で明示的にPromiseを返すこともできる。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
async function myAsync() { return new Promise((resolve, reject) => { // var err = new Error('エラーが発生しました!'); var err = ''; if (!err) { resolve('Hello Async!'); }else { reject(err); } }) } myAsync() .then((data)=>{console.log(data);}) |
メソッドチェーンやエラーハンドリングの仕方も基本的にPromiseと同様。
awaitの使い方
async関数の中ではawaitを宣言できる。こうすることで、非同期関数の中で別の非同期関数を呼び出した時に、呼び出した非同期関数の結果を待つことができる。(つまり処理に順序性をつけ同期的に処理できる)
async関数以外の場所(トップレベルなど)でawaitを宣言するとエラーになる。
awaitの基本的な使い方
直列に処理(メソッドチェーン)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Promise処理 function myPromise() { return new Promise((resolve) => { resolve('Hello Promise!'); }) } // Async関数 async function myAwait() { var promise_result = await myPromise(); //awaitを使ってmyPromiseの結果を待ち受け console.log(promise_result); //出力 console.log('Hello Await!'); //次にAsync関数として結果を出力 } myAwait() |
【実行結果】
1 2 |
Hello Promise! Hello Await! |
この順序性はたとえsetTimeout()
などを使っていても制御可能(ただし直列に処理するので時間がかかるようになる)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// Promise処理 function myPromise() { return new Promise((resolve) => { setTimeout(()=>{ resolve('Hello Promise! <=処理開始から3秒後に表示される'); }, 3000); }) } // Async関数 async function myAwait() { var promise_result = await myPromise(); //awaitを使ってmyPromiseの結果を待ち受け console.log(promise_result); //出力 setTimeout(()=>{ console.log('Hello Await! <=処理開始から5秒後に表示される'); //次にAsync関数として結果を出力 }, 2000); } myAwait() |
【実行結果】
1 2 |
Hello Promise! <=処理開始から3秒後に表示される Hello Await! <=処理開始から5秒後に表示される |
エラーハンドリング
awaitを使う場合エラーハンドリングはtry { ... } catch { ... }
で書く。(catch()
を使って書く方法もあるが割愛)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// Promise処理 function myPromise() { return new Promise((resolve, reject) => { var err = new Error('エラーが発生しました!'); // var err = ''; if (!err) { resolve('Hello Promise!'); }else { reject(err); } }) } // Async関数 async function myAwait() { try { var promise_result = await myPromise(); //myPromiseからエラーが返ってくる console.log(promise_result); //awaitでエラーを待ち受けたので console.log('Hello Await!'); //この2行は実行されない } catch (e) { console.log(e); //myPromiseのエラーをハンドリングし出力 } } myAwait() |
実行結果
1 2 |
Error: エラーが発生しました! [~以下省略~] |
【ユースケース】 async, awaitを使ってNode.jsからOSコマンドを同期的に実行する
npmのchild_process.exec
というモジュールを使ってNode.jsから子プロセスを生成してOSコマンドを実行することができます。
しかし、このexecは非同期処理なのでなにも考えずに実行するとOSコマンドの処理結果を待たずにNode側の処理が終了するなんてことになります。
async, awaitを使ってOSコマンドの実行結果を待ち受けてNode側の処理を完了させます。
(execSyncというモジュールがあることにはこの際目をつぶってください。execの方が標準エラーの扱いが直感的に理解しやすかったのでこっちを使ってます)
async, awaitを使わずに実行
1 2 3 4 5 6 7 8 9 10 11 12 |
// モジュール読み込み const exec = require('child_process').exec; // async, awaitを使わず実行 var cmd = 'sleep 3;echo \"3秒経過。OSコマンド終了。\"'; exec(cmd, (err, stdout, stderr)=> { if (err) { console.log(stdout + stderr); } else { console.log(stdout); } }); console.log('Node.js処理終了。'); |
【実行結果】
1 2 |
Node.js処理終了。 3秒経過。OSコマンド終了。 |
sleep 3
のせいでconsole.log('Node.js処理終了。');
が先に実行されてしまいます。
async, awaitを使って実行
shell(cmd)
というOSコマンドを実行する非同期関数と、それを呼び出すmain()
というasync関数を作り、main()
を実行するという流れです。
main()
の中ではawaitを使ってOSコマンドとNode.js側の処理の順序を制御しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
// モジュール読み込み const exec = require('child_process').exec; // コマンド実行の非同期関数 // 引数:コマンドライン // 戻り値:stdout/stderrのpromise function shell(cmd) { return new Promise((resolve, reject) => { exec(cmd, (err, stdout, stderr)=> { if (err) { reject(stdout + stderr); } resolve(stdout); }); }); } // main処理 async function main() { var cmd = 'sleep 3;echo \"3秒経過。OSコマンド終了。\"'; try { var result = await shell(cmd); console.log(result); console.log('Node.js処理終了。'); } catch (e) { console.log(e); process.exit(1); } } main(); |
【実行結果】
1 2 3 |
3秒経過。OSコマンド終了。 Node.js処理終了。 |
OSコマンドの結果をawaitで待ち受けてからconsole.log('Node.js処理終了。');
が実行されていることがわかります。