【2019年6月版】fs.writeFile したら ERR_INVALID_CALLBACK ですって

## Qiitaの記事、10000件?!

こちらの記事「[Qiitaの記事をランダムに読める API / サービス を4時間ぐらいで作った](https://qiita.com/redshoga/items/b550cdce94d7b74287d5)」を見て私も「Qiitaの最新記事10000件、取ってみよ！」と思いついたのでございます。

## さっそくコーディング

上記記事のGithubリポジトリ [https://github.com/redshoga/random-qiita-api](https://github.com/redshoga/random-qiita-api) を参考にしつつ、`軽量な Http Client` らしい 「[phin](https://github.com/ethanent/phin)」を使って意気揚々とプログラムを作成いたしました。

まずはプロジェクトの準備から。

```console
$ npm init
...
$ npm i phin
```

で〜`package.json`の`scripts`に`"start":"node indes.js",`を追記して`npm start`コマンドで`index.js`が実行されるように仕掛け、以下のような感じで`index.js`を作成いたします。

```javascript:index.js
const p = require('phin');
const fs = require('fs');

(async ()=>{
    const TOKEN = '...';

    // 1ページあたり100件は固定
    const q = ((n) => (p) => `?page=${p}&per_page=${n}`)(100);

    for(let i = 1 ;i <= 100; i++) {
        const res = await p({
            'url' : 'https://qiita.com/api/v2/items' + q(i),
            'headers' :{
                'Authorization' : `Bearer ${TOKEN}`
            },
            'parse': 'json'});
        fs.writeFile(`./items/qiita_item_${i}.json`, JSON.stringify(res.body, '', ' '));
    }
})();
```

Qiita の APIは認証キーがないと[1時間に60回までのリクエストしか送れない](https://qiita.com/api/v2/docs#%E5%88%A9%E7%94%A8%E5%88%B6%E9%99%90)ため、短時間に100ページを取得して「最新の10000万件」を取ってくることができないのです。
というわけで、`TOKEN` で定義している認証キーをこちらの「[Qitta APIのヘルプ#認証認可](https://qiita.com/api/v2/docs#認証認可)」のページを参照して取得します。今回は手っ取り早く、"ユーザーの管理画面"からサクッと取得してきました。

ついでにクエリパラメータの作成で、ちょっぴりカリー化(「[3歳娘「パパ、関数をカリー化して？」](https://qiita.com/Yametaro/items/99cc1c8ebcfc703b1410)」をかじった程度)を試してみたり、[jsonのフォーマットでインデント指定](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify)してみたり。

## そして実行、[ポチッとな！](https://dic.pixiv.net/a/%E3%83%9C%E3%83%A4%E3%83%83%E3%82%AD%E3%83%BC)

いよいよ `npm start` で実行してみます！

```console
$ npm start
...
(node:13149) UnhandledPromiseRejectionWarning: TypeError [ERR_INVALID_CALLBACK]: Callback must be a function
    at maybeCallback (fs.js:128:9)
    at Object.writeFile (fs.js:1158:14)
...
```

**ERR_INVALID_CALLBACKッ!** **ERR_INVALID_CALLBACKッッ!!!** ~~ガーン！！！(￣◇￣;)！！！~~

`writeFile` で `Callback must be a function` のエラーってなんだそりゃ？！

## node.jsの公式ドキュメントをちゃんと確認

はい、node.jsの公式ヘルプを覗くと・・・
[https://nodejs.org/dist/latest-v10.x/docs/api/fs.html#fs_fs_writefile_file_data_options_callback](https://nodejs.org/dist/latest-v10.x/docs/api/fs.html#fs_fs_writefile_file_data_options_callback)

> fs.writeFile(file, data[, options], callback)

・・・いつの間にか必須になっていたんですね、第3引数。ていうか、非同期になっていたのですね・・・
ってことで、`callback` は必須です。

ちなみに、更新履歴は・・・

|Version| Changes|
|---|---|
|v10.10.0| The data parameter can now be any TypedArray or a DataView.|
|v10.0.0 | The callback parameter is no longer optional. Not passing it will throw a TypeError at runtime. |
|v7.4.0  | The data parameter can now be a Uint8Array.|
|v7.0.0  | The callback parameter is no longer optional. Not passing it will emit a deprecation warning with id DEP0013.|
|v5.0.0  | The file parameter can be a file descriptor now.|
|v0.1.29 | Added in: v0.1.29 |

Oh... `The callback parameter is no longer optional. ` の文字が踊る。。。
`v.7` の時点で必須だったかぁ・・・

というわけで、以下のようにコールバックを追加して再チャレンジいたしました。

```javascript:index.js
const p = require('phin');
const fs = require('fs');

(async ()=>{
    const TOKEN = '....';

    // 1ページあたり100件は固定
    const q = ((n) => (p) => `?page=${p}&per_page=${n}`)(100);

    for(let i = 1 ;i <= 100; i++) {
        const res = await p({
            'url' : 'https://qiita.com/api/v2/items' + q(i),
            'headers' :{
                'Authorization' : `Bearer ${TOKEN}`
            },
            'parse': 'json'});
        fs.writeFile(`./items/qiita_item_${i}.json`, JSON.stringify(res.body, '', ' '), (err)=>{
            if(err) console.log(`error!::${err}`);
        });
    }
})();
```

はい、こちらのコードで無事にjsonファイル100個、10000件の記事データが取得できました。

このjsonでこれから〇〇なことや、××をことを試してみたいと思います♪ :sunglasses:

## 参考ページ

- [Qiitaの記事をランダムに読める API / サービス を4時間ぐらいで作った](https://qiita.com/redshoga/items/b550cdce94d7b74287d5)
- [Github: Qiitaの最新の10000件の記事からランダムでURLを取得するAPI](https://github.com/redshoga/random-qiita-api)
- [Github: Phin](https://github.com/ethanent/phin)
- [Phin Document](https://ethanent.github.io/phin/)
- [Qiita API v2 ドキュメント](https://qiita.com/api/v2/docs)
- [3歳娘「パパ、関数をカリー化して？」](https://qiita.com/Yametaro/items/99cc1c8ebcfc703b1410)
- [JSON.stringify() - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify)
- [File System | Node.js v10.16.0 Documentation](https://nodejs.org/dist/latest-v10.x/docs/api/fs.html#fs_fs_writefile_file_data_options_callback)

## 宣伝

JSネタ、こちらもどうぞ〜！

- [【2019年6月版】jsでクロージャに渡す変数はとりあえずconstしておけッ！！](https://qiita.com/koinori/items/f2cd26622cf33bb2f805)