あるファイルをダウンロードする時など、リクエストの前に別のリクエストを発行する必要があるとき、その処理はコールバックで複雑になりがちです。
import request from 'request'
function requestLatestTarball (name, callback) {
const url = 'https://registry.npmjs.org'
const urlInfo = `${url}/${name}`
request(urlInfo, {json: true}, (error, {body}) => {
if (error) {
return callback(error)
}
const version = body['dist-tags'].latest
const urlLatestTarball = `${url}/${name}/-/${name}-${version}.tgz`
request(urlLatestTarball, {encoding: null}, (error, {body}) => {
if (error) {
return callback(error)
}
return callback(null, {uri: urlLatestTarball, buffer: body})
})
})
}
requestLatestTarball('jquery', (error, tarball) => {
console.log(error, tarball)
// null { uri: 'https://registry.npmjs.org/jquery/-jquery-2.2.4.tgz',
// buffer: <Buffer 7b 22 65 72 72 6f 72 22 3a 22 70 61 63 6b 61 67 65 20 63 6f 75 6c 64 20 6e 6f 74 20 62 65 20 66 6f 75 6e 64 2e 22 7d> }
})
例として紹介するプログラムは、npmのレジストリからパッケージ情報を取得し、最終バージョンの
tgz
ファイルをダウンロードします。1
また、巨大なファイルを取り扱いたいので、readableStream/writebleStream を利用して、オーバーヘッドを減らしたいと思いました。
具体的に、request(urlLatestTarball).pipe
をすれば良いのですが、関数からstreamを返し、writableStream
にpipe
できる実装を求めました。
requestLatestTarball('jquery').pipe(createWriteStream('jquery-latest.tgz'))
上記を実装するためには、requestLatestTarball
が1つ目のリクエストを完了させてから、2つ目のリクエストをpipeするreadableStream
を返す必要があるのですが、そこで利用できたのが stream.PassThrough でした。
これは、何もしないstreamを作成し、他のreadableStreamを.pipe
で受け取ることで、任意のタイミングでデータ送出を行えます。以下は単純な利用例です。
import {createReadStream, createWriteStream} from 'fs'
import {PassThrough} from 'stream'
const passThrough = new PassThrough()
setTimeout(() => {
createReadStream('foo.txt').pipe(passThrough)
}, 500)
passThrough.pipe(createWriteStream('bar.txt')).on('finish', () => {
console.log('書き込みおわりましたゾ')
})
echo 'beep' > foo.txt
babel-node pass-through.js
# 書き込みおわりましたゾ
cat bar.txt
# beep
これを冒頭のrequestLatestTarball
に適用できます。
import request from 'request'
import {PassThrough} from 'stream'
import {createWriteStream} from 'fs'
function requestLatestTarball (name, callback) {
const passThrough = new PassThrough()
const url = 'https://registry.npmjs.org'
const urlInfo = `${url}/${name}`
request(urlInfo, {json: true}, (error, {body}) => {
if (error) {
return passThrough.emit('error', error)
}
const version = body['dist-tags'].latest
const urlLatestTarball = `${url}/${name}/-/${name}-${version}.tgz`
request(urlLatestTarball).pipe(passThrough)
})
return passThrough
}
requestLatestTarball('jquery').pipe(createWriteStream('jquery-latest.tgz'))
例外処理はpassThrough
に集約され、引数にコールバックが不要になったため、見通しの良いコードになりました。
-
公開済みのライブラリとして、同様の機能を https://github.com/59naga/npm-tarball で提供しています。 ↩