Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9030

[Node.js] 非同期処理 - コールバック編

$
0
0

コールバック

コールバックを利用する非同期プログラミングは、JavaScriptにおける非同期プログラミングの実装パターンとして最も基本的なものです。このパターンでは、非同期に処理を行う関数に引数として渡したコールバックが処理完了時に実行されます。

まずは簡単に"setTimeout()"を使用したコールバックを実行してみます。

setTimeout(()=>console.log('1秒通過しました'),1000);console.log('setTimeout()を実行しました。');>>>setTimeout()を実行しました1秒通過しました

実行すると、「setTimeout()」の処理を待たずに「console.log()」が実行されている。
コールバックは自動機のインターフェースとして用いられるが、コールバックは必ず非同期処理というわけではない。

constarray1=[0,1,2,3,4]constarray2=array1.map(v=>{console.log(`${v}を変換します。`)returnv*10});console.log(array2)>>>0を変換します1を変換します2を変換します3を変換します4を変換します[0,10,20,30,40]

エラーハンドリング

JavaScriptではエラーハンドリングのためにtry...catch構文が用意されています。
try...catchではコールバックの中で発生したエラーをハンドリングできない。

constparseJSONAsync=(json,next)=>{try{setTimeout(()=>next(JSON.parse(json)),1000);}catch(err){console.log('エラーをキャッチしました。',err);next({})}}parseJSONAsync('json',result=>console.log('parse結果',result))>>>SyntaxError:UnexpectedtokenjinJSONatposition0

Node.jsは通常、エラーがイベントループまで到達するとprocessオブジェクトからuncaughtExceptionいベントが発行され、アプリケーションが停止します。uncaughtExceptionを補足し、これを握りつぶしてアプリケーションの停止を食い止めることも可能ですが、この結果アプリケーションの整合性が保証されない状態になるため推奨されません。

process.on('uncaughtException',err=>process.exit(1))

コールバックの中では起こりうるアプリケーションエラーを適切にキャッチし、それをイベントループまで到達させることなく呼び出し元に返すことが重要です。

constparseJSONAsync=(json,next)=>{setTimeout(()=>{try{next(null,JSON.parse(json));}catch(err){next(err)}},1000)}parseJSONAsync('json',(err,result)=>console.log('parse結果',err,result))>>>parse結果SyntaxError:UnexpectedtokenjinJSONatposition0

同期と非同期を混ぜると危険

同じ文字列に対してJSONAsync()を実行した結果は常に同じになるはずですから、この結果をキャッシュして使い回してみます。

constcache={}constparseJSONAsyncWithCache=(json,callback)=>{constcached=cache[json]if(cached){callback(cached.err,cached.result)return}parseJSONAsync(json,(err,result)=>{cache[json]={err,result}callback(err,result)})}parseJSONAsyncWithCache('{"message": "Hello", "to": "world"}',(err,result)=>{console.log('1回目の結果',err,result)parseJSONAsyncWithCache('{"message": "Hello", "to": "world"}',(err,result)=>console.log('2回目の結果',err,result))console.log('2回目の呼び出し完了')})console.log('1回目の呼び出し完了')>>>1回目の呼び出し完了1回目の結果null{message:'Hello',to:'world'}2回目の結果null{message:'Hello',to:'world'}2回目の呼び出し完了

この実装は、JavaScriptのよく知られたアンチパターンです。ここでの問題は、parseJSONAsyncWithCache()が状況によってコールバックを同期的に実行したり非同期的に実行したりすることです。
コールバックの呼び出しが同期的か非同期かで一貫性がないと、APIの挙動が予期しづらくなってしまいます。今回の例では1回目の呼び出しでは"console.log('1回目...')"の後にコールバック関数が実行されているのに対して、2回目はその順番が逆になっています。状況によってこうした順番が前後するのは、複雑で原因の特定が困難な不具合の原因になりかねない。
これを書き直すと次のようになる。

constcache2={}constparseJSONAsyncWithCache=(json,callback)=>{constcached=cache2[json]if(cached){// キャッシュに値が存在する場合でも、非同期的にコールバックを実行するsetTimeout(()=>callback(cached.err,cached.result),0)return}parseJSONAsync(json,(err,result)=>{cache2[json]={err,result}callback(err,result)})}parseJSONAsyncWithCache('{"message": "Hello", "to": "world"}',(err,result)=>{console.log('1回目の結果',err,result)parseJSONAsyncWithCache('{"message": "Hello", "to": "world"}',(err,result)=>console.log('2回目の結果',err,result))console.log('2回目の呼び出し完了')})console.log('1回目の呼び出し完了')>>>1回目の呼び出し完了1回目の結果null{message:'Hello',to:'world'}2回目の呼び出し完了2回目の結果null{message:'Hello',to:'world'}

これで1回目も2回目も一貫性が貯めたれるようになりました。
非同期的に行っている"setTimeout"を"process.nextTick()"に置き換えても問題ありません。
process.nextTick()はsetTimeout()よりもはやくコールバックを実行するため、すぐに値を返したいという要求により適している。
ただし、process.nextTick()はブラウザ環境のJavaScriptには存在しないAPIのため、ブラウザ上でも動かすコードを書いている場合には使えない。
ブラウザと共有して使用する場合には、Web標準に由来するグローバルメソッドのqueueMicrotask()を利用するとよい。

コールバックヘル

複数の非同期処理を逐次的に実行する場合、非同期処理のコールバックの中で次の非同期処理を実行する必要があります、これを繰り返した結果、コードのネストが深く可読性や保守性が損なわれる状態になります。この状態をコールバックヘルと呼びます。これはJavaScriptの最もよく知られたアンチパターンです。

asyncFunc1(input,(err,result)=>{if(err){...}asyncFunc2(input,(err,result)=>{if(err){...}asyncFunc3(input,(err,result)=>{if(err){...}})})})

解決策としては、コードを分割することです。

functionfirst(arg,callback){asyncFunc1(input,(err,result)=>{if(err){...};second(result,callback);});}functionsecond(arg,callback){asyncFunc2(input,(err,result)=>{if(err){...};third(result,callback);});}functionthird(arg,callback){...}

Viewing all articles
Browse latest Browse all 9030

Trending Articles