3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Discord.js Clientのlogin/destroyとpromise/イベントの関係

Posted at

Discord.jsのClientクラスは、Discordサーバとの接続やBotの情報を参照するためのインターフェースを提供してくれる。サーバとの接続/切断は2つのメソッドlogin/destroyで行い、接続状態の変化は4つのイベントready, reconnecting, resume, disconnectで知ることができるようになっている。

このClientを複数の機能から共有して、必要な時だけ接続しようとしたところ、期待した動きと違うことに気付いた。いくつか思いつく限り調べてみたので結果をまとめる。

期待したのと違う動き

login後にreadyイベントが発生しないうちにdestroyすると接続が切れなかった。てっきり中断してくれるかと思ったが、そういうわけではなかった。以下の例を実行すると、readyイベントしか発生せず、接続は保たれたままになってしまう。

接続が切断できなくなる例
let client = new Client();
client.on( 'ready',        () => { console.log( 'ready' ) } );
client.on( 'disconnect',   () => { console.log( 'disconnect' ) } );
client.on( 'reconnecting', () => { console.log( 'reconnecting' ) } );
client.on( 'reconnecting', () => { console.log( 'resume' ) } );

client.login( TOKEN );
client.destroy();

他にも、ready後にloginするとpromiseが完了しない(=awaitできない)ことも分かった。

login/destroyとpromise/イベントの関係

Clientの動きを確かめた結果(実験は以降「login/destroyとpromise/イベントの関係の実験」参照)をまとめると以下の表のようになる。

  • login
  • 戻り値のpromiseはreadyへの変化と関係ありそうだが、資料に明記なかった
  • 接続済(ready/resume)でコールすると、promiseは完了しない(例外も起きない)★
  • 切断中(destroy後)にコールすると、切断したのち接続する(切断とは無関係?)
  • destroy
  • promiseは実際の切断と無関係に完了する
  • 接続中にdestroyしても、接続を中断しない★
  • ready/disconnectイベント
  • login/destroyと関係なく、クライアントの状態が変わった時に発生する。(これは普通)
初期状態 試したこと イベント promise
未接続 login後readyを待たたずにlogin readyが1回だけ発生 全login/destroyのpromiseが完了
未接続 login後readyを待たずにdestroy readyが1回だけ発生(disconnectなし) 全login/destroyのpromiseが完了
未接続 loginせずにdestroy 発生せず 全login/destroyのpromiseが完了
接続済 destroyせずにlogin 発生せず ready後のloginのpromiseが完了せず★
接続済 destroy後disconnectを待たずにdestroy disconnectが1回だけ発生 全destroyのpromiseが完了
接続済 destroy後disconnectを待たずにlogin disconnect、readyを繰り返す★ destroy後のloginのpromiseが完了せず(1回目だけ)★

この動作が意味すること

logindestroyの振る舞いが外から見えない内部状態(実際にはStatusがあるらしいが)で変わるということ。例えば、接続中にloginしても、promiseは完了しないしreadyイベントもないので、いつクライアントが使えるようになるかを知ることができない。

素のままだと複数のモジュールから1つのClientを共有するのは難しそうな気がする。Clientを拡張し、接続・切断の完了を戻り値のpromiseの完了で知ることができるようにすれば良いかもしれない。

login/destroyとpromise/イベントの関係の実験

未接続状態からの操作

loginのPromiseとreadyイベントどちらが早い?

loginの戻り値であるPromiseの発生と、readyイベントはどちらが先になるか、discord.jsの資料に記載がない。loginの戻り値は「Token of the account used.」と書かれているだけで、readyも「Emitted when the client becomes ready to start working.」と書かれているだけなので、両者の関係は分からない。「使えるようになる」という意味で見ると、ready後に操作した方が良いとは思う。

実験の結果、readyイベントの方が先にあることが分かった。

    let client = new Client();
    client.on( 'ready',        () => { console.log( 'ready' ) } );
    client.on( 'disconnect',   () => { console.log( 'disconnect' ) } );

    await client.login( TOKEN );
    console.log( 'login promsie' );
    client.destroy();
// 結果:
// ready
// login promise
// disconnect

login後readyを待たずにlogin

readyイベントの方が早いのは変わらず、readydisconnectイベントは1つずつしか起きないことが分かった。loginのpromiseはそれぞれ完了していた。

    let client = new Client();
    client.on( 'ready',        () => { console.log( 'ready' ) } );
    client.on( 'disconnect',   () => { console.log( 'disconnect' ) } );

    console.log( 'login 1回目' )
    client.login( TOKEN ).then( () => console.log( 'login promise(1)' ) );
    console.log( 'login 2回目' )
    client.login( TOKEN ).then( () => console.log( 'login promise(2)' ) );
    console.log( 'login 3回目(待機)' )
    await client.login( TOKEN ).then( () => console.log( 'login promise(3)' ) );

    client.destroy();

// 結果:
//    login 1回目
//    login 2回目
//    login 3回目(待機)
//    ready
//    login promise(1)
//    login promise(2)
//    login promise(3)
//    disconnect

login後readyを待たずにdestroy

loginをコール後、接続している途中でdestroyをコールすると、readyイベントの前にdestroyのpromiseが完了することが分かった。disconnectイベントは発生せず、プログラムが終了した時点で接続が保持されていた。

接続している途中 = 接続していないから、destroyがすぐに成功したのかもしれない。後の操作が勝つのではなかった。

    let client = new Client();
    client.on( 'ready',        () => { console.log( 'ready' ) } );
    client.on( 'disconnect',   () => { console.log( 'disconnect' ) } );

    console.log( 'login' );
    client.login( TOKEN ).then( () => console.log( 'login promise' ) );
    console.log( 'destroy' );
    client.destroy().then( () => console.log( 'destroy promise' ) );

// 結果:
//    login
//    destroy
//    destroy promise
//    ready
//    login promise

loginせずにdestroy

接続していない状態でdestroyをコールすると、promiseがすぐに完了し、イベントは発生しないことが分かった。状態が変化しないからだと思う。

    let client = new Client();
    client.on( 'ready',        () => { console.log( 'ready' ) } );
    client.on( 'disconnect',   () => { console.log( 'disconnect' ) } );

    console.log( 'destroy 1回目' );
    client.destroy().then( () => console.log( 'destroy promise(1)' ) );
    console.log( 'destroy 2回目' );
    client.destroy().then( () => console.log( 'destroy promise(2)' ) );

// 結果:
//    destroy 1回目
//    destroy 2回目
//    destroy promise(1)
//    destroy promise(2)

接続済(ready後)からの操作

destroyせずにlogin

ready後のloginは完了しないことが分かった。Promiseがsolveせず、awaitで待機しようとすると次に進めなくなり、destroyに到達しなくなってしまった。

    let client = new Client();
    client.on( 'ready', async () => {
        console.log( 'ready' ); 
        
        console.log( 'login ready後 1回目' );
        client.login( TOKEN ).then( () => console.log( 'login promise(1)' ) );

        console.log( 'login ready後 2回目(待機)' );
        await client.login( TOKEN ).then( () => console.log( 'login promise(2)' ) );
        
        console.log( 'destroy' );
        client.destroy().then( () => console.log( 'destroy promise' ) );
    } );
    
    client.on( 'disconnect',   () => { console.log( 'disconnect' ) } );

    console.log( 'login' ); 
    client.login( TOKEN ).then( () => console.log( 'login promise' ) );

// 結果:
//    login
//    ready
//    login ready後 1回目
//    login ready後 2回目(待機)
//    login promise

destroy後disconnectを待たずにdestroy

ready後、destroyを続けてコールすると、readyのコールバック内でpromiseまで完了することが分かった。コールバックが抜けた後、loginのpromiseが完了し、切断後disconnectイベントが発生した。

loginと違い、destroyは素直な動きをしている。

    let client = new Client();
    client.on( 'ready', async () => {
        console.log( 'ready' ); 
        
        console.log( 'destroy 1回目' );
        client.destroy().then( () => console.log( 'destroy promise(1)' ) );

        console.log( 'destroy 2回目' );
        client.destroy().then( () => console.log( 'destroy promise(2)' ) );
    } );
    client.on( 'disconnect',   () => { console.log( 'disconnect' ) } );

    console.log( 'login' ); 
    client.login( TOKEN ).then( () => console.log( 'login promise' ) );
    
// 結果:
//    login
//    ready
//    destroy 1回目
//    destroy 2回目
//    destroy promise(1)
//    destroy promise(2)
//    login promise
//    disconnect

destroy後disconnectを待たずにlogin

ready後、destroyをコール氏、すぐにloginをコールすると、そのpromiseは完了しないものの、disconnect後再度readyになることが分かった。以降、切断と接続を繰り返してしまう。しかも、2回目以降はready後のloginのpromiseも成功するようになって不可解。

先ほどのlogin中のdestroyと合わせて考えると、loginの方が強いのかもしれない。

    let client = new Client();
    client.on( 'ready', async () => {
        console.log( 'ready' ); 
        
        console.log( 'destroy' );
        client.destroy().then( () => console.log( 'destroy promise(1)' ) );

        console.log( 'ready後 login' );
        client.destroy().then( () => console.log( 'after ready login promise' ) );
    } );
    client.on( 'disconnect',   () => { console.log( 'disconnect' ) } );

    console.log( 'login' ); 
    client.login( TOKEN ).then( () => console.log( 'login promise' ) );

// 結果:
//    login
//    ready
//      destroy
//      ready後 login
//      destroy promise(1)
//    login promise
//    disconnect
//   ここから2回目の接続
//    ready
//      destroy
//      ready後 login
//      destroy promise(1)
//      after ready login promise
//    disconnect
//   ここから3回目の接続
//    ready
//      destroy
//      ready後 login
//      destroy promise(1)
//      after ready login promise
//    disconnect
3
0
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
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?