普段何気なく使っている技術や言葉について、表面的な知識にせず、しっかりとイメージできるまで自分なりに調べて整理し、理解するシリーズ。
目次
JavaScript非同期処理が可能で並行処理はできない
JavaScriptはシングルスレッドで動いており、並行処理はできません。
JavaScriptでは、キューに登録された関数が順番にひとつずつ実行され、その順番は同期や非同期であったりします。
この非同期というのは、サーバーと通信を行いリクエストが返るのを待っている間など、その処理を待たないで次へ進めることです。
コールバック
コールバックとは
コールバックとは、処理が終わったら指定した関数が呼ばれる方法です。関数の引数に別の関数を指定する感じです。
最近のモダンな開発ではコールバックを使うことはほとんどありませんが、JavaScriptにおける非同期処理を理解する上でもコールバックについて理解する必要があります。
コールバック関数/高階関数による処理
引数として渡される関数のことをコールバック関数と呼び、別の関数を引数にとる関数を高階関数と呼びます。
以下の例では、showPrice関数を引数としてbicycle関数を呼び出しています。この場合ですとshowPrice関数がコールバック関数となり、bicycle関数が高階関数となります。
1 2 3 4 5 6 7 8 9 10 | function bicycle(price, func){ const name = '自転車'; func(name, price); } const showPrice = function(name, price){ console.log(name + ' の現在価格は ' + price + ' 円です。'); } bicycle(20000, showPrice); |
コールバック関数は、引数に直接記述することも可能です。
1 2 3 4 5 6 7 8 | function bicycle(price, func){ const name = '自転車'; func(name, price); } bicycle(20000, function(name, price){ console.log(name + ' の現在価格は ' + price + ' 円です。'); }); |
コールバック地獄
例えば、いくつかのファイルの読み込みが終わった後にそのデータに対して処理をしたい場合、以下のようにコールバック関数を次々に書いていくことになります。
この例では3つファイルに対して処理をかけていますが、これがどんどん増えていくとさらにネストが深くなることで可読性が下がり、メンテナンスが大変になります。
1 2 3 4 5 6 7 8 9 | const fs = require('fs'); fs.readFile(a.txt', function(resA) { fs.readFile(b.txt', function(resB) { fs.readFile(c.txt', function(resC) { console.log(resA + resB + resC); }) }) }) |
また、エラー処理を個別に書いていくとさらにコードが複雑化していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | const fs = require('fs'); fs.readFile('a.txt', function(err, resA) { if (err) { console.log(err); return; } fs.readFile('b.txt', function(err, resB) { if (err) { console.log(err); return; } fs.readFile('c.txt', function(err, resC) { if (err) { console.log(err); return; } console.log(resA + resB + resC); }) }) }) |
この非同期処理におけるコールバック地獄を解消するためにPromiseがES2015で導入されました。
Promise
Promiseとは
Promiseとは、JavaScriptでの非同期処理のコールバック関数をより簡潔に記述するための仕組みです。
Promise を用いることで、非同期処理の成功や失敗に対するハンドラーを関連付けることができます。これにより非同期処理のメソッドは、最終的な値を返すのではなく、未来のある時点で値を持つPromiseを返すことで、同期メソッドと同じように値を返すことができるようになります。
Promiseはその名の通り「今は値を返せないけどあとでちゃんと返すよ」と約束するオブジェクトです。
Promiseの状態
Promise の状態は以下のいずれかとなります。
初期状態はpendingで、一度fulfilledまたはrejectedになったらそれ以降は状態は変わらず、非同期処理の終了時に返す値もそれ以降は変わりません。
- pending : まだ処理が終わっていない状態
- fulfilled : 処理が成功した状態
- rejected : 処理が失敗した状態
Promiseの使い方
resolve, reject
1 2 3 4 5 6 7 | new Promise(function(resolve, reject) { resolve('success'); }); new Promise(function(resolve, reject) { reject('failed'); }); |
Promiseはresolveとreject、2つの関数を引数に取ります。
- resolve : 処理が成功したときのメッセージを表示する関数
- reject : 処理が失敗したときのメッセージを表示する関数
then(), catch()
Promise#then(onFulfilled, onRejected)
then()は2つの関数を引数に取ります。Promiseの状態がfulfilledになったら1番目が、rejectedになったら2番目のコールバック関数が実行されます。
Promise#catch(onRejected)
then(undefined, onRejected) のショートカットとして機能し、返り値 promiseで引数の onRejectedのコールバック関数が実行されます。
公式にもありましたが、エラー処理がすぐに必要がない場合は、最後の.catch() 文までエラー処理をしない方がシンプルに書くことができます。
参考:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | const myPromise = (new Promise(myExecutorFunc)) .then(handleFulfilledA,handleRejectedA) .then(handleFulfilledB,handleRejectedB) .then(handleFulfilledC,handleRejectedC); // または、おそらく次の方がよい ... const myPromise = (new Promise(myExecutorFunc)) .then(handleFulfilledA) .then(handleFulfilledB) .then(handleFulfilledC) .catch(handleRejectedAny); |
Promiseによる非同期処理
以上を踏まえて、先程のコールバック地獄をPromiseで書き換えると以下のようになります。だいぶスッキリして見やすくなりました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | let a, b, c readFile('a.txt') .then(function(resA) { a = resA; return readFile('b.txt'); }) .then(function(resB) { b = resB; return readFile('c.txt'); }) .then(function(resC) { c = resC; console.log(a + b + c); }) .catch(function(err) { console.log(err); }) |
async/await
async/awaitとは
async/awaitは非同期処理の構文のことで、Promiseを利用した場合よりも、簡潔に非同期処理を書くことができます。
asyncとは
asyncとは、非同期関数を定義する関数宣言のことで、以下のように関数の前にasyncを宣言することにより、非同期の関数を定義できます。
async functionはPromiseを返却します。
1 2 3 | async function getSuccess() { return 'success' } |
ちなみにPromiseで書くと以下のようになります。
1 2 3 | function getSuccess() { return Promise.resolve('success') } |
awaitとは
async function内でPromiseの結果が返ってくるまで待機する演算子のことです。
awaitを指定した関数のPromiseの結果が返されるまで、async function内の処理を一時停止し、結果が返されたらasync function内の処理を再開します。
1 2 3 4 5 6 | async function showResult() { const result = await getResult(); // getResult()のPromiseの結果が返ってきてから実行される console.log(result); } |
async/awaitによる非同期処理
以上を踏まえて先程のコードをasync/awaitで書き直してみます。
Promiseよりもさらにわかりやすくスッキリなりました。
1 2 3 4 5 6 7 8 9 | async function showFilesData() { const resA = await readFile(a.txt'); const resB = await readFile(b.txt'); const resC = await readFile(c.txt'); console.log(resA + resB + resC); } showFilesData() .catch(err) {console.log(err)} |
参考
JavaScriptの非同期処理を理解する その1 〜コールバック編〜 | さくらのナレッジ