react-routerの人がgithub上で以下のようなコードを公開されていたのですが、内容を理解するのに時間が掛かったので解説します。
[1,2,3,4,5].reduceRight((next,arg)=>{
return ()=>{
console.log(arg)
next()
}
},()=>{
console.log('all done')
})()
// 1
// 2
// 3
// 4
// 5
// all done
reduceRight
は配列の値を、reduceRight
のコールバック関数内で右から順番に処理します。
最後の行で})()
としているように、reduceRight
の戻り値を、関数として実行しています。
実行したのは、reduceRight
で最後に返したarg=1
を持つクロージャ関数です。
このクロージャ関数のnext
は、reduceRight
から渡されたarg=2
を持つクロージャ関数です。
つまり、next
を実行すると、reduceRight
にreturn
した関数を、直列に左から実行します。
arg=5
のクロージャ関数には、本来next
は未定義ですが、これはreduceRight
の第二引数initialValue
から渡すことが出来ます。
その結果、コンソール上には"5"
のあとに"all done"
が表示されています。
閑話休題
冒頭に取り上げた実コードは以下の様なものでした。
const ResolveExtensions = [ '', '.js', '.json' ]
/**
* Resolves a path like "lib/index" into "lib/index.js" or
* "lib/index.json" depending on which one is available, similar
* to how require('lib/index') does.
*/
function resolveFile(file, callback) {
ResolveExtensions.reduceRight(function (next, ext) {
return function () {
statFile(file + ext, function (error, stat) {
if (stat && stat.isFile()) {
callback(null, file + ext)
} else if (error && error.code !== 'ENOENT') {
callback(error)
} else {
next()
}
})
}
}, callback)()
}
これを以下のようなtree上で実行すると、結果は以下のようになります
tree .
# .
# ├── bar.js
# ├── baz.json
# └── foo
resolveFile('foo',(error,found)=>{
console.log(error,found) // null 'foo'
})
resolveFile('bar',(error,found)=>{
console.log(error,found) // null 'bar.js'
})
resolveFile('baz',(error,found)=>{
console.log(error,found) // null 'baz.json'
})
resolveFile('beep',(error,found)=>{
console.log(error,found) // undefined undefined
})
おわりに
直列の非同期処理を簡潔に書く方法として、reduceRight
を利用してみるのは如何でしょうか。
自分も、非同期でフォルダを上に登りながらファイルを検索したい時に、参考になりました。
climb-lookup-v1.0.0 - lookup the file while climbing to recursively. like a require().