質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.52%

  • JavaScript

    16389questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

javascriptのwaitに関する質問です。

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 2
  • VIEW 616

neil

score 17

前提・実現したいこと

以下のコードでサーバーのGPUの状態をsshコマンドを用いて
得ようとしているのですが、arrayの中身がundefiedであるというエラーがでてうまくいきません。
問題点は、まだarrayの中に情報が入っていないのにもかかわらず、
sliceをしてしまっていることです。
解決法として、arrayの中にきちんと要素が入ってから
slice処理を行うということをしたいのですが、
どのようにすれば良いのかわかりません。

よろしくお願いします。

発生している問題・エラーメッセージ

Uncaught TypeError: Cannot read property 'slice' of undefined

該当のソースコード

function query_gpu(gpu_name, array){
        var SSH = require('simple-ssh');
        var ssh = new SSH({
            host: gpu_name,
            user: 'name',
            pass: 'pass'
        });

        ssh.exec('nvidia-smi --query-gpu=utilization.gpu --format=csv', {
            out: function(stdout) {
                array.push(stdout)
                console.log(stdout)
            }
        }).start()

    }




    var a = [];


    query_gpu('tai', a);
    array[1] = Number(array[1].slice(0,2));
    array[2] = Number(array[2].slice(0,2));

試したこと

// Mixing jQuery and Node.js code in the same file? Yes please!

$(function(){

function query_gpu(gpu_name, array, callback) {
var SSH = require('simple-ssh');
var ssh = new SSH({
host: gpu_name,
user: 'name',
pass: 'pass'
});

ssh.exec('nvidia-smi --query-gpu=utilization.gpu --format=csv', {
out: function(stdout) {
array.push(stdout)
debugger;
console.log(stdout)
callback();
}
}).start()
}

var a = [];
query_gpu('tai', a, function() {
// ここに取得したデータについての処理を全て書く
a[1] = Number(a[1].slice(0, 2));
a[2] = Number(a[2].slice(0, 2));
});

});
を走らせてみて途中のstdoutの結果を見ました。
'''
utilization.gpu [%]
0 %
99 %
'''
と複数行で3つの値が来ることを期待していたのですが、実際は初めのutilization.gpu [%]だけでした。

補足情報(言語/FW/ツール等のバージョンなど)

nodeのsimple-ssh

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • turbgraphics200

    2017/02/07 08:42

    出力結果は utilization.gpu [%] 0 % 97 % が一行となるのでしょうか。それとも複数行になるのでしょうか。出力結果はわかりやすくコードと同様に```で囲ってください。

    キャンセル

  • neil

    2017/02/07 08:47

    コマンドラインで打つと複数行になります

    キャンセル

  • kei344

    2017/02/07 11:15

    質問文のコードはそれぞれコードブロックで囲んでいただけませんか? ```(バッククオート3つ)で囲み、前後に改行をいれるか、コードを選択して「<code>」ボタンを押すとコードブロックになります。

    キャンセル

回答 3

+6

JavaScriptにはwaitという概念はありません。ファイルやDBの取得や実行、通信などの通常は待ちが発生するような処理は、開始と共にバックグラウンドで行われ、終了を待たずに次のコードへ進みます。これを非同期処理と言います。取得内容を使用するには、ファイルの取得等が終了した後に実行されるコールバック関数で全ての処理をする必要があります。

※ Node.jsのfs等には、処理を待ってくれるsync系のメソッドも一部だけ存在します。

simple-sshも同様で、コールバック内でしか、その結果を利用できません。今回のコードでは、outで指定しているコールバック内で全て処理するか、処理する関数をコールバック内から呼び出す必要があります。次のように書きかえれば、sshでの取得内容を利用できるようになります。

function query_gpu(gpu_name, array, callback) {
  var SSH = require('simple-ssh');
  var ssh = new SSH({
    host: gpu_name,
    user: 'name',
    pass: 'pass'
  });

  ssh.exec('nvidia-smi --query-gpu=utilization.gpu --format=csv', {
    out: function(stdout) {
      array.push(stdout)
      console.log(stdout)
      callback();
    }
  }).start()
}

var a = [];
query_gpu('tai', a, function() {
  // ここに取得したデータについての処理を全て書く
  a[1] = Number(a[0].slice(0, 2));
  a[2] = Number(a[0].slice(0, 2));
});

これではコールバックがどんどん深くなって、コールバックだらけになるのでは無いのか?と思うかも知れません。その通りです。これはいわゆるコールバック地獄と言われるもので、JavaScriptのコードをわかりにくいものにするものの一つと言われています。これを解決するにはPromise等を使う必要があります。Promiseの使い方は色々と文献がありますので、調べてみてください。

まとめると次のようになります。

  • IOや実行、通信等の待ちが発生するような処理をJavaScriptは待ってくれず、非同期で行う。
  • その結果はコールバック内(またはコールバックから呼び出した関数内)でしか利用できない。
  • コールバックだらけになるコールバック地獄を解消するには、Promise等を使う必要がある。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/07 08:24

    丁寧な回答ありがとうございます。
    コードをデバッグモードで走らせてみました。
    期待していたのはstdoutの中にutilization.gpu [%] 0 % 97 %があることを期待していたのですが、
    実際の中身はutilization.gpu [%] だけでした。arrayの中身もutilization.gpu [%]だけでした。
    全てをarrayの中に入れるにはどうしたら良いでしょうか?
    よろしくお願いします。

    キャンセル

  • 2017/02/07 19:09

    うーん、ちょっとわからないですね…。SSHが失敗したのかsimple-sshがおかしいのか、一行ずつ取ってくるのか…。
    turbgraphics200さんの回答通りだと、全部返ってくるはずですが、もしかしたら、一行ずつなのかもしれません。一行ずつとるのであれば、別途カウントしながら、全部終わったら(カウントが一定値になったら)、callback()を呼ぶとかにするといいかと思います。

    キャンセル

checkベストアンサー

+2

outが非同期で呼ばれるからというのはすでに前者の回答者により説明がありましたが。
実際このoutの関数がどのように呼ばれるのか、特に標準出力の結果の1行ごとに複数回呼ばれるのか、すべての行一括にして一回で呼ばれるのかによって違うかと思います。
一応自分の環境でこのsimple-sshを試してみたら、nvidiaマシンではないのでこのコマンドは使えませんので代わりにlsコマンドを実行してみたら、標準出力結果を全行一括で1回のみ呼ばれました。もしかするとコマンドによって違うかもしれませんが。

function query_gpu(gpu_name, array) {
    return new Promise((resolve, reject) => {

        var SSH = require('simple-ssh');
        var ssh = new SSH({
            host: gpu_name, 
            user: 'name', 
            pass: 'pass'
        });

        ssh.exec('nvidia-smi --query-gpu=utilization.gpu --format=csv', {
            out: function (stdout) {
                if (stdout.includes('\n')) {
                    // stdoutが全行の場合
                    array = stdout
                        .split('\n')
                        .filter(v => !!v.trim() && !v.includes('utilization.gpu'))
                        .map(v => v.split(' '))
                        .filter(v => !isNaN(v[0]))
                        .map(v => +v[0]);
                } else {
                    // stdoutが1行ずつの場合
                    if (stdout.includes('utilization.gpu')) return;
                    var arr = stdout.split(' ').filter(v => v.trim());
                    if (!arr.length || isNaN(arr[0])) return;
                    array.push(+arr[0]);
                }
            },
            exit: function () {
                funcResult(array);
            }
        }).start();
    });
}

function funcResult(array) {
    console.log(array);
}

query_gpu('tai', []);

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+1

raccyさんの助言通りでできるはずなので、コールバックが正確に行われているか、データが本当に代入されたか確認してみた方が良いですね。

それと、proxy使ってwatchするというやり方もなくはないのですが、遊んでみるのも楽しそうですね!
参考:Proxy - MDN

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.52%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • JavaScript

    16389questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。