今回は、JavaScriptのPromiseについての紹介と、Promiseを使っていると困る「例外時のエラー出力」について書きます。
Promiseとは
Promiseは非同期処理を分かりやすく記述するために使われるものです。例えば、Node.jsで3つのファイルを読み込むような場合を考えます。ひとつ前のファイルの内容を使って次のファイルを読み込むイメージです。
この場合、readFileSyncを使うなどの回避ができると思いますが、Promiseの説明のため非同期処理で行います。
まずは、Promiseを使わずに書いてみます。
1 2 3 4 5 6 7 8 9 10 11 |
const fs = require( 'fs' ); fs.readFile( 'file1', function ( error, data1 ) { // data1から次に読み込むファイル名を求める fs.readFile( data1, function ( error, data2 ) { // data2から次に読み込むファイル名を求める fs.readFile( data2, function ( error, data3 ) { // data3を使った処理 } ); } ); } ); |
このように非同期処理で読み込みを行おうとすると、ネストが深くなり見づらいという欠点があります。
そこで、Promiseを使うとネストを深くせずに非同期処理を記述できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
const fs = require( 'fs' ); // readFile関数のPromiseを返す関数を作成する function readFile( ...args ) { return new Promise( function ( resolve, reject ) { fs.readFile( ...args, function ( error, data ) { if ( error ) reject( error ); else resolve( data ); } ); } ); } readFile( 'file1' ).then( function ( data1 ) { // data1から次に読み込むファイル名を求める return readFile( data1 ); } ).then( function ( data2 ) { // data2から次に読み込むファイル名を求める return readFile( data2 ); } ).then( function ( data3 ) { // data3を使った処理 } ).catch( function ( error ) { // いずれかのreadFileでエラーがあった場合の処理 console.log( error ); } ); |
気をつけなければいけない点として、catchの記述忘れがあります。catchで指定した関数はPromiseの中で例外が起きた場合に呼ばれます。
これを記述しないと、例外が起きたことを知るすべがなく、コンソールなどにもなにも出力されません。
例外時のエラー出力
catchで例外を知ることはできますが、スタックトレースを通常の例外が投げられたときのようにコンソールへ出力する場合、一工夫が必要です。process.nextTickもしくはsetTimeout(もしくはlodashのdefer)を使うことで、通常のようにエラーを再送できます。
以下のようにconsole.errorで出力した場合はスタックトレースが表示されないため、デバッグをすることが難しいです。
1 2 3 4 5 6 7 8 9 10 11 |
( new Promise( function ( resolve, reject ) { throwFunc(); } ) ).catch( outError ); function throwFunc() { throw new Error( 'error text.' ); } function outError( error ) { console.error( error ); // [Error: error text.] } |
これに対して、以下のようにsetTimeoutを使うことで、通常の例外発生時と同じように、スタックトレースのエラーを表示することができます。
1 2 3 4 5 6 7 8 9 10 11 |
( new Promise( function ( resolve, reject ) { throwFunc(); } ) ).catch( timeoutThrow ); function throwFunc() { throw new Error( 'error text.' ); } function timeoutThrow( error ) { setTimeout( function () { throw error; } ); } |
このようなエラー出力になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/path/to/source.js:10 setTimeout( function () { throw error; } ); ^ Error: error text. at throwFunc (/path/to/source.js:6:8) at /path/to/source.js:2:2 at Object. (/path/to/source.js:1:65) at Module._compile (module.js:413:34) at Object.Module._extensions..js (module.js:422:10) at Module.load (module.js:357:32) at Function.Module._load (module.js:314:12) at Function.Module.runMain (module.js:447:10) at startup (node.js:142:18) at node.js:939:3 |
他にも以下のような関数で同じことができます。利用しているライブラリや環境に合わせて使い分けてください。
1 2 3 4 5 6 7 8 9 |
// Node.jsのnextTickを使った場合 function nextTickThrow( error ) { process.nextTick( function () { throw error; } ); } // lodashを使った場合 function deferThrow( error ) { _.defer( function () { throw error; } ); } |
詳しくPromiseについて知りたい場合は、Promise - JavaScript | MDNが参考になります。
キー・ポイントでは、
「ネスト深くなったな」で思考を放置しないエンジニアを募集しています。
・2017新卒の方はリクナビ2017へ ( 第2弾選考 エントリー受付中です )
・中途の方はコチラへ