LoginSignup
6
7

More than 5 years have passed since last update.

複雑なリクエストをPassThroughにまとめる

Last updated at Posted at 2016-06-09

あるファイルをダウンロードする時など、リクエストの前に別のリクエストを発行する必要があるとき、その処理はコールバックで複雑になりがちです。

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を返し、writableStreampipeできる実装を求めました。

requestLatestTarball('jquery').pipe(createWriteStream('jquery-latest.tgz'))

上記を実装するためには、requestLatestTarballが1つ目のリクエストを完了させてから、2つ目のリクエストをpipeするreadableStreamを返す必要があるのですが、そこで利用できたのが stream.PassThrough でした。

これは、何もしないstreamを作成し、他のreadableStreamを.pipeで受け取ることで、任意のタイミングでデータ送出を行えます。以下は単純な利用例です。

pass-through.js
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に集約され、引数にコールバックが不要になったため、見通しの良いコードになりました。


  1. 公開済みのライブラリとして、同様の機能を https://github.com/59naga/npm-tarball で提供しています。 

6
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
7