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().