Callback 使いから Promise マスターにクラスチェンジするぞ！

現在私は、非同期処理をするときは `callback` を使っています。
ですが、ネストが深くなるにつれて混乱してしまいます。( ﾟДﾟ)＜ﾒﾀﾞﾊﾟﾆ‼

Node.js には `promise` という callback 地獄を解消するためのオブジェクトが用意されています。
`promise` での処理を覚えれば、見やすいコーディングが可能です！

…と、言うことは理解できているのですが、何故か未だにあやふやな promise 処理。
こりゃもう、身体で覚えるしかねぇ！( ﾟДﾟ)
憧れの PROMISE マスターに、なりたいな。ならなくちゃ。絶対になってやるー！(*‘∀‘)

幸い（？）なことに前回作った LINEWORKS の BOT 登録プログラムが callback 地獄になっているので、こちらを promise に書き換えてみたいと思います。

（前回の記事）[API を使って LINEWORKS BOT を登録する](https://qiita.com/kunihiros/items/bad2063ee4a8bb6e7dae)

```js:registerBot.js

const api_id = "api id";
const consumer_key = "consumer key";
const token = "server list token";
const account_id = "ryo_saeba@hunter.city";
const domain_id = 00000000;
const callback_url = "https;//www.xyz.jp/callback";

// トーク Bot のテナント登録
const request = require('request');
const uri_text = "https://apis.worksmobile.com/" + api_id;
let options = {
    uri: uri_text + "/message/registerBot/v4",
    headers: {
        "Content-type": "application/json",
        "consumerKey": consumer_key,
        "Authorization": "Bearer " + token
    },
    json: {
        "name": "test bot",
        "photoUrl": "https://developers.worksmobile.com/favicon.png",
        "description": "defeat the promise process bot",
        "managerList": [account_id]
    }
};
request.post(options, (error, response, body) => {
    if(body.errorMessage) return;
    let botNo = body.botNo;
    // トーク Bot のドメイン登録
    options.uri = uri_text + "/message/registerBotDomain/v3"
    options.json = {
        "botNo": botNo,
        "domainId": domain_id,
        "usePublic": true,
        "usePermission": false
    }
    request.post(options, (error, response, body) => {
        if(body.errorMessage) return;
        // メッセージ受信サーバー追加
        options.uri = uri_text + "/message/setCallback/v2";
        options.json = {
            "botNo": botNo,
            "callbackUrl": callback_url,
            "callbackEventList": ["text", "sticker", "image"]
        };
        request.post(options, (error, response, body) => {
            if(body.errorMessage) return;
            // トーク Bot からメッセージ送信
            options.uri =  uri_text + "/message/sendMessage/v2",
            options.json = {
                "botNo": botNo,
                "accountId": account_id,
                "content": {"type":"text","text":"BOT 登録が完了しました（＾ω＾）"}
            };
            request.post(options, (error, response, body) => {
                console.log(body);
                if(body.errorMessage) return;
                console.log("success");
            });
        });
    });
});

```



このコードを promise 処理したコードに置き換えていくのですが、このあとしばらく私の恥ずかしい失敗談が続きますので**「お前の失敗なんざどうでもいいから成功したコードを見せてくれ！」**という一般的なご意見をお持ちの方は、是非！読み飛ばして一番最後の完成コードをご確認くださいませ。

## promise に置き換えるには（失敗談）

さぁ、それではこの callback たちを promise に置き換えてみましょう！
「簡単だよ！ .then でつなげばいいんだよ！」という先輩エンジニアのアドバイスの元、よく理解していないまま私が書いたコードがこちらです！

```js:registerBot.js

// パラメータ等は前回と同じなので割愛
const request = require('request');
const uri_text = "https://apis.worksmobile.com/" + api_id;

// トーク Bot のテナント登録
new Promise((resolve,reject) => {
    request.post(options, (error, response, body) => {
        (body.errorMessage) ? reject() : resolve(body.botNo);
    })
}).then((botNo) => { // トーク Bot のドメイン登録
    options.uri = uri_text + "/message/registerBotDomain/v3"
    options.json = {
        "botNo": botNo,
        "domainId": domain_id,
        "usePublic": true,
        "usePermission": false
    }
    request.post(options, (error, response, body) => {
        (body.errorMessage) ? reject() : resolve(botNo);
    })
}).then((botNo) => { // メッセージ受信サーバー追加
    options.uri = uri_text + "/message/setCallback/v2";
    options.json = {
        "botNo": botNo,
        "callbackUrl": callback_url,
        "callbackEventList": ["text", "sticker", "image"]
    };
    request.post(options, (error, response, body) => {
        (body.errorMessage) ? reject() : resolve(botNo);
    })
}).then((botNo) => { // トーク Bot からメッセージ送信
    options.uri =  uri_text + "/message/sendMessage/v2",
    options.json = {
        "botNo": botNo,
        "accountId": account_id,
        "content": {"type":"text","text":"BOT 登録が完了しました（＾ω＾）"}
    };
    request.post(options, (error, response, body) => {
        if(body.errorMessage) reject();
        console.log("success");
        resolve();
    });
});

```

今見ると、ひっどいですねぇ～。恥さらしですな。
のちに、このコードを見た先輩が「なんでこうなるのか、本気でわけわからん」とマジ顔でおっしゃられました。
きっと、今この記事を読んでいる皆様も同じ顔をしているのでしょう。
恥ずかしさのあまり、つい山籠もりをしてしたくなりますが、後回しにして先に進みます。

上記コードですが、もちろんエラーになりました！(/・ω・)/

`ReferenceError: reject is not defined`

ようは「**なんで何度も reject してんの？**」ってことです。
コンピュータさん、頭の悪い子でごめんなさい。

promise にとって、`reject` は一回こっきり。
最初に `new` するときだけなのです。

元のコードでやっていたエラー処理を `return` から単に `reject` に置き換えたのがいけない。
理解が浅いからこういうことになるのです。
まったく恥ずかしいやつですね！(/ω＼)

## .catch でエラー処理をする

ってなわけで、たくさんの `reject` をひとつにすべく `.catch` でエラー処理をしていきます！
promise オブジェクトでは `.catch` でエラー処理いっぺんに指定できてしまうんです！
なんて素晴らしい仕様！
処理が複雑になり、`.then` の数が増えていっても、エラー処理は１つだけ。
わかりやすいし、見やすいですね～。
ってなわけで `.catch` を追加してエラー処理を１つにまとめたものがコチラ。

```js:registerBot.js

// パラメータ等は前回と同じなので割愛
const request = require('request');
const uri_text = "https://apis.worksmobile.com/" + api_id;

// トーク Bot のテナント登録
new Promise((resolve,reject) => {
    request.post(options, (error, response, body) => {
        (body.errorMessage) ? reject() : resolve(body.botNo);
    })
}).then((botNo) => { // トーク Bot のドメイン登録
    options.uri = uri_text + "/message/registerBotDomain/v3"
    options.json = {
        "botNo": botNo,
        "domainId": domain_id,
        "usePublic": true,
        "usePermission": false
    }
    request.post(options, () => {});
}).then((botNo) => { // メッセージ受信サーバー追加
    options.uri = uri_text + "/message/setCallback/v2";
    options.json = {
        "botNo": botNo,
        "callbackUrl": callback_url,
        "callbackEventList": ["text", "sticker", "image"]
    };
    request.post(options, () => {});
}).then((botNo) => { // トーク Bot からメッセージ送信
    options.uri =  uri_text + "/message/sendMessage/v2",
    options.json = {
        "botNo": botNo,
        "accountId": account_id,
        "content": {"type":"text","text":"BOT 登録が完了しました（＾ω＾）"}
    };
    request.post(options, () => {});
}).catch((err) => { // エラー処理
    console.log(err);
});

```

おー！見やすくなったぞー(*´▽｀)
などと最初は喜んだおバカな私。もちろんエラーになりましたとさ。ぎゃふん。

`TypeError: Cannot read property 'botNo' of undefined`

まぁ、ちゃんとエラーが `catch` できていたので良しとしましょう。

## return で次の .then に値を渡す

さて、肝心のエラー内容ですが「**botNo が空っぽだよ**」と言われています。
そう、`return` していないので、次の `.then` に何にも渡していないんですよね！
超絶おバカですね。

`return botNo;`

`return` 文をそれぞれの `.then` に追加して、これで大丈夫だろう！と実行した私。
エラーメッセージ出なかったぞ！やったぁ！成功だ！

と、喜んだのも束の間。待てど暮らせど登録完了メッセージが送信されてこない。
「トーク Bot からメッセージ送信 API」の実行ログを見てみると、以下のエラーが。

`{ errorMessage: 'Service fail, HTTP/1.1 400 Bad Request, {"code":400,"message":"Bad Request Parameters: Cannot access bot"}',
  errorCode: '090',
  code: 'SERVICE_UNAVAILABLE' }`

ふむ。Bot にアクセスできないとな？・・・なんで？
これ、２つ目の「トーク Bot のドメイン登録」をしていないメッセージ送信すると起きる現象なんですよね。
試しにもう一度トーク Bot からメッセージ送信 API を叩いてみる。…成功。

つまり、だ。今はメッセージ送信ができて、登録時には、できない。
`.then` で非同期処理をしているはずなのに、ドメイン登録時の `request` の結果を待たずに次の `.then` に進んでいって、ドメイン登録完了前にメッセージを送信している？
つまーり！非同期処理になってないっ！！どうして！？

## .then 内の request は結局 promise 化していない

どうしても何も、私がよく理解していないまま使ったのが悪いのです。反省。orz
色々と調べてみたところ、`.then` の中の `request.post()` は promise 化されていない模様。
promise 化されていないってことは非同期処理されていないってことで、もちろん順番はめちゃめちゃに。

よし、トーク Bot のテナント登録の際に promise 化した request を使おう！
と、そこでふと思った。いちいち request を promise 化するの、面倒くさくね？
面倒くさいということは！誰かが便利なものを作っているはず！

そうして私は `request-promise` と出会いました。先輩の皆様、ありがとうございます。
では、さっそく `npm install request-promise` してコードを書き替えていきたいと思います。

```js:registerBot.js

// パラメータ等は前回と同じなので割愛
const request = require('request-promise');

let botNo;
// トーク Bot のテナント登録
request(options).then((body) => {
    botNo = body.botNo;
    // トーク Bot のドメイン登録
    options.uri = uri_text + "/message/registerBotDomain/v3"
    options.json = {
        "botNo": botNo,
        "domainId": domain_id,
        "usePublic": true,
        "usePermission": false
    }
    return request(options);
}).then((body) => {
    // メッセージ受信サーバー追加
    options.uri = uri_text + "/message/setCallback/v2";
    options.json = {
        "botNo": botNo,
        "callbackUrl": callback_url,
        "callbackEventList": ["text", "sticker", "image"]
    };
    return request(options);
}).then((body) => {
    // トーク Bot からメッセージ送信
    options.uri =  uri_text + "/message/sendMessage/v2",
    options.json = {
        "botNo": botNo,
        "accountId": account_id,
        "content": {"type":"text","text":"BOT 登録が完了しました（＾ω＾）"}
    };
    return request(options);
}).catch((err) => {
    console.log(err);
});

```

`return request(options);` に書き換えているので、`botNo` が次の `.then` に渡っていかないことに注意。
一番最初の `body` で `botNo` を変数に入れておきましょう！

さぁ、実行してみましょう！ドン！

![1558664439.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363367/3a794cac-fcfd-c4db-cc8b-e88804ae8530.png)

だーいせーこーう♪　(*‘∀‘)＜promise bot ゲットだぜ！

思った通りの動作をしてくれたので、良かった良かったホッと一息。
これで終わった！と、思いきや…。
実はある落とし穴があったことに、このときの私は気づいていなかったのだ。。。

## エラー処理ができていない！？

さっき、`.catch` でエラー処理の設定をしました。
API ID や TOKEN などのパラメータの入力が間違っていた場合には、`.catch` に飛んでいってエラーを表示してくれるはず。
しかし、`.catch` には飛ばず、次の `.then` に進んでいってしまい、エラーを連発しているのです！

```
register-tenant
{ errorMessage: 'API ID not exists',
  errorCode: '052',
  code: 'NOT_FOUND' }
register-domain
{ errorMessage: 'API ID not exists',
  errorCode: '052',
  code: 'NOT_FOUND' }
register-callback
{ errorMessage: 'API ID not exists',
  errorCode: '052',
  code: 'NOT_FOUND' }
send-message
{ errorMessage: 'API ID not exists',
  errorCode: '052',
  code: 'NOT_FOUND' }
```

ナントイウコトデショウ

## LINEWORKS API のエラーは .catch しない

これ、今回大変勉強になったところです。
例えばなんですが、`request` する際に `uri` が `NULL` であるとか、パラメータの型指定が間違っているとかだと、ちゃんと `.catch` してくれるのですよ。
でも、LINEWORKS API からの返答がエラーだった場合には `.catch` してくれないのです。

**request 自体は成功しているから、API がエラーだって言おうが request は成功してるもん！**
ってことなんですね。

API に届いた時点で、成功。
その先でエラーだった場合には返ってきた値を見て `.catch` に投げる必要があるんですね。
`try ~ catch` における `throw` をする必要があるんですね。

### throw しないで、reject しよう

JavaScript Promise の本に書かれていますが、`promise` オブジェクトを使うときは `Promise.reject` を使います。
`body` の中を見てエラーだったら `Promise.reject(body);` とすれば `.catch` に飛んでエラー表示してくれます。

LINEWORKS API からの Response がエラーかどうかを判別するには、`errorCode` または `errorMessage` が含まれているかを判定すれば良いでしょう。
なので、下記一文を諸所に追加します。

`if(body.errorMessage) return Promise.reject(body);`

これで、ついに完成となりました！

# 完成したコードです！

```js:registerBot.js
const api_id = "api id";
const consumer_key = "consumer key";
const token = "server list token";
const account_id = "ryo_saeba@hunter.city";
const domain_id = 00000000;
const callback_url = "https;//www.xyz.jp/callback";

const request = require('request-promise');
const uri_text = "https://apis.worksmobile.com/" + api_id;

// トーク Bot のテナント登録
let options = {
    method: "POST",
    uri: uri_text + "/message/registerBot/v4",
    headers: {
        "Content-type": "application/json",
        "consumerKey": consumer_key,
        "Authorization": "Bearer " + token
    },
    json: {
        "name": "test bot",
        "photoUrl": "https://developers.worksmobile.com/favicon.png",
        "description": "defeat the promise process bot",
        "managerList": [account_id]
    }
};
let botNo;
console.log("register-tenant");
request(options).then((body) => {
    if(body.errorMessage) return Promise.reject(body);
    console.log(body);
    botNo = body.botNo;
    // トーク Bot のドメイン登録
    options.uri = uri_text + "/message/registerBotDomain/v3"
    options.json = {
        "botNo": botNo,
        "domainId": domain_id,
        "usePublic": true,
        "usePermission": false
    }
    console.log("register-domain");
    return request(options);
}).then((body) => {
    if(body.errorMessage) return Promise.reject(body);
    console.log(body);
    // メッセージ受信サーバー追加
    options.uri = uri_text + "/message/setCallback/v2";
    options.json = {
        "botNo": botNo,
        "callbackUrl": callback_url,
        "callbackEventList": ["text", "sticker", "image"]
    };
    console.log("register-callback");
    return request(options);
}).then((body) => {
    if(body.errorMessage) return Promise.reject(body);
    console.log(body);
    // トーク Bot からメッセージ送信
    options.uri =  uri_text + "/message/sendMessage/v2",
    options.json = {
        "botNo": botNo,
        "accountId": account_id,
        "content": {"type":"text","text":"BOT 登録が完了しました（＾ω＾）"}
    };
    console.log("send-message");
    return request(options);
}).then((body) => {
    if(body.errorMessage) return Promise.reject(body);
    console.log(body);
}).catch((err) => {
    console.log("error:")
    console.log(err);
});
```

うん、長い！( ﾟДﾟ)

#おわりに
ここまでお付き合いいただきありがとうございました。いや、本当に。

ここまでの道のりも長かったし、比例して記事も長くなってしまいました。
結局、コードの長さ的には `Callback` と変わらなかったなぁ。

でも！ネストしていないから見やすいし、`promise` オブジェクトの構造がわかってると、こっちの方が拡張や修正はしやすいかも！

`promise.finally` とか `Promise.all` とか、まだ仲良くなれていない子たちがいるから、憧れの PROMISE マスターへの道のりはまだまだ遠いけど、諦めずに頑張りたいと思います！（＾ω＾）

ではまた！(^^)/

#参考にさせていただきましたm(_ _)m

[JavaScript Promiseの本](http://azu.github.io/promises-book/)
[LINEWORKS Developers](https://developers.worksmobile.com/jp/document?lang=ja)

[JavaScriptの同期、非同期、コールバック、プロミス辺りを整理してみる](https://qiita.com/YoshikiNakamura/items/732ded26c85a7f771a27)
[今更だけどPromise入門](https://qiita.com/koki_cheese/items/c559da338a3d307c9d88)
[CoffeeScriptでPromiseを使ったときにハマった](https://qiita.com/MikamiHiroaki/items/ba57742d6020282864a3)
[request-promiseを使ったHTTPクライアントを作る](https://qiita.com/you21979@github/items/37d62a7d190505bc089a)