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

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

ただいまの
回答率

91.05%

  • Node.js

    1488questions

    Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

  • 非同期処理

    86questions

    非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Promiseの使い方がいろんなドキュメントを読んでも全然わからない。

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 5
  • VIEW 673

MOTOMUR

score -41

こんにちは。
「全然わからない」は流石に言い過ぎなのですが。
やっぱり使いこなすには至らず質問させていただきます。

自分の理想的な使い方。

やはり、非同期処理の嫌なところは書いた順番に処理し終えて行ってくれないため、それを回避するためにpromiseを使いたいですよね。
「これをやったら、次にこれをやって、これをやったら次これをやる」というように。

色々ドキュメントを漁ってみて。この使い方が一番、上で言ったような使い方ができると思いました。

var p1 = new promise(function(resolve,reject){
  // any function
})
var p2 = p1.then(function(value){
  // any function
})


これで多分、p1をやったらp2をやるっていうコードは書けてると思うのですが。あってますかね?

不明点。

上記の使い方があっていれば不明点は三つです。(最後の一つはJSの初歩的な質問かもしれません。。。お付き合いいただけると助かります。)

①上記のような方法だと、p1内部の処理の完全な終了を待って、p2が発火するのでしょうか?

例えば、p1で値を受け取り、p2ではその値を用いて処理をします。と言ったものがあるとします。
(補足ですが、p1 p2で想定している処理は、外部のデータベースより値を参照し、取ってくると言った非同期処理です。今回の場合はfirebase.databaseです。)
完全にp1の処理が終わってからp2の処理になってもらわないと、p2で参照した値はundefinedとなってしまいます。

②resolve,rejectってなんのためにあるの?

処理が成功するとresolve、エラーになるとrejectと言った漠然とした理解ですが。まずこれは正しいですか?

また。正しいと仮定して話を続けますが、promiseで連続させている次のfunction内に前の値を受け渡すのに、resolve,rejectを使用するのですか?
あまりなんのためにあるのか有用性が見えないのですが、どう便利なのでしょうか?

③連続したpromise内での変数の扱い。

これはpromiseに限った話ではなく、js全体の初歩的な話なのですが、ずっとviewのコーディングではstateでファイル内で共通して使う変数を管理してきたため。nodejsとして変数をいざ扱おうと思った時に混乱してしまいました。なので質問させてください。

以下の三つの①、②、③それぞれの出力はどうなりますでしょうか。

//there is root of file
var outside = "this is out"
any function(){
  var inside_any = "this is inside any"
  function(){
    var most_inside = "Hi"
  }
  function(){
    console.log(outside + inside_any + most_inside)
    //①はこの部分です。
  }

 var p1 = new promise(function(resolve){
    var inside_promise = "hello"
  })
  var p2 = p1.then(function(resolve){
    console.log(inside_promise)
    //②はこの部分です。
  })

  console.log(inside_any + most_inside + inside_promise)
  //③はこの部分です。
}


このような使い方は正しいのでしょうか?
現在どのように変数の受け渡しが可能か手探りでやっていますので、至らない質問かもしれませんが、教えていただけると助かります。
また、このような使い方以外が必要な場合、どのような処理で対応していますでしょうか?

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 2

checkベストアンサー

+4

やはり、非同期処理の嫌なところは書いた順番に処理し終えて行ってくれないため、それを回避するためにpromiseを使いたいですよね。

非同期処理の根本の問題はPromiseを使おうが何しようが変わりません。
Promiseを使うメリットは一重にコードのネストを減らしたいが為に行うものです。

resolve,rejectってなんのためにあるの?

従来の非同期処理は、予め後続の処理を関数に閉じ込めて引数として渡します。
概ねこんな感じになるでしょう。

const client = require('何かしらのクライアント');
client.get(url, function(err, code, body){
  if (err) {
    console.error(err);
    return;
  }
  if (code != 200) {
    console.error('ステータスコードが正常ではありません');
    return;
  }
  bodyを解析
});

慣習としてコールバック引数は最終引数であること、
コールバック引数として渡した関数の第一引数はerrであるというルールが存在します。
何故か?処理が正常終了したか異常終了したかが見分けられないからです。

const client = require('何かしらのクライアント');
// このtry-catchは無駄
try {
  client.get(url, function(err, code, body){
    if (err) {
      throw err;
    }
    if (code != 200) {
      throw new Error('ステータスコードが正常ではありません');
    }
    // 省略
  });
} catch (e) {
  console.error(e);
}

基本的にJSの非同期処理は関数を引数として渡してあげて、
「終わったらこれ実行しといてね?あとはよろしく!」といった形で正常終了します。
try-catchで受け取れそうな気がしますが、見事スルーされてしまいます。

つまり、非同期処理の場合はこの第一引数を見張り続ける必要があります。
長く深いネスト構造はイライラの元です。
しかもひたすらネストされ続けるのでイライラがMAXになるわけです。

promiseはこの問題にメスを入れました。
失敗時には何時でもrejectを叩いて逃げられます。
また慣習である第一引数のerrがなくなり、コードも簡素になります。
利用者側の視点ではこのようにバリデートと処理を分離することも可能になりました。

const client = require('何かしらのクライアント + promise');
client
  .get(url)
  .then(function(code, body) {
    if (code != 200) {
      return Promise.reject('ステータスコードが正常ではありません');
    }
    return body;
  })
  .then(function(body) {
    // 処理
  })
  .catch(function(err) {
    console.error(err);
  });

これがpromiseのrejectの威力で、
見事try-catchに似たような事が出来ることが確認できます。
コードのネストもこれ以上に深くなる事はまずありません。
使う側になって初めてメリットが見えてきますね。

client.getはPromiseを返す関数になっています。
内部で失敗した場合「第一引数にerrを突っ込む」という慣習から解き放たれている事がわかります。
更に後続の処理でPromise.reject()でcatchに合流させる事も簡単に出来ます。

以下の三つの①、②、③それぞれの出力はどうなりますでしょうか。

考え方のヒントですが、JavaScriptは関数の度にスコープを作ります。
関数がネストされる度に、親子関係のスコープが作られます。
子供の中身で変数が定義されていない場合、親のスコープで変数を探します、それでも無ければ更に親の…と辿っていき、グローバル領域で定義されていなければ変数未定義エラーとなります。

因みに①は単なる関数定義なので発火しません。
(function(){ '処理' })()のように括弧で包んだ後に()で実行してください。

var most_inside = "Hi"は関数で包んだ中で実行しています。
関数で作られたスコープはお外に出てくる際になくなり、そのスコープ内で定義していたmost_insideもなくなります。
もし関数を(fn)()という即時実行関数に変更して無理やり動作させた場合、
①の出力結果はoutside + inside_anyになるでしょう(そもそもmost_insideの変数未定義エラーで落ちますが)

p1とp2はpromiseの定義ですね。(maisumakunさんの仰るように変ですが)
thenで繋いでも、関数同士のスコープが共有されるわけではありません。
従って②も変数未定義エラーで落ちます。

③の時点ですが、p1とp2はpromiseの定義を行うだけで素通りするので、
Promiseの実行より前に実行されるでしょう。
まぁそれとは別にmost_inside, inside_promiseの両方が未定義エラーで落ちます。
理由は①や②と同じく関数のスコープの問題です。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/01/15 21:28

    丁寧な回答ありがとうございます。
    理解が深まり、今回自分がやりたかったことはようやく理解ができました。

    Promiseのチェーンですが、今回の自分のコードでいうと、
    function fetchUid(ChannelId) {
    return new Promise(function(resolve, reject) {
    admin.database().ref('youtubeChannels/' + ChannelId).once('value').then(function(snapshot){
    uid = snapshot.val().uid
    resolve(uid)
    })
    })
    }

    function fetchAuthor(uid) {
    var result = [uid]
    return new Promise(function(resolve, reject) {
    admin.database().ref('users/' + uid).once('value').then(function(snapshot){
    result.push(snapshot.val().user)
    resolve(result)
    })
    })
    }

    fetchUid(ChannelId)
    .then(fetchAuthor)
    .then(function(result){
    console.log(result[0]+result[1])

    このようにすると、二つ前のプロミスの結果も今のthenに受け渡せました。

    浅い理解ですが、resolve(value)とすると、次のthen内のfunction(some)のsomeにvalueに値を代入するということだと思いました。

    キャンセル

  • 2018/01/15 22:02

    お疲れ様です。
    素晴らしい出来です!

    両方の結果が欲しければオブジェクトを使いまわしても良さそうですね。
    (コメント欄はネストが死ぬので全角スペース1個を採用しています。)

    function fetchUid(state) {
     const ChannelId = state.ChannelId;
     return new Promise(function(resolve, reject) {
      // 省略
      state.uid = uid;
      resolve(state);
     })
    }
    function fetchAuthor(state) {
     const uid = state.uid;
     return new Promise(function(resolve, reject) {
      // 省略
      state.user = snapshot.val().user
      resolve(state);
     }
    }
    fetchUid({ChannelId: Channelid})
     .then(fetchAuthor)
     .then(function(state){
      console.log(state);
     })
     .catch(function(err){
      console.error(err);
     })

    キャンセル

+3

まず気づいた点ですが、Promise.prototype.then2つの引数を取ります。ということで、 p2 = p1.then(function(resolve,reject){という書き方は正しくありません。

  • 第1引数…resolve時に呼び出される関数。Promise自体の結果は、この関数への引数というかたちで伝えられます。
  • 第2引数…reject時に呼び出される関数。

なお、成功時の値はコールバック関数の引数として渡されますので、変数のスコープもほぼ関係しません。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/01/14 21:53

    質問文のコード間違ってましたね。修正しておきます。

    理解力不足かもしれないですが、確認させてください。
    var p1 = new promise(function(resolve,reject){
    var uid = "any"
     resolve(uid)
    })
    var p2 = p1.then(function(value){

    }
    こう記述すると、p2はp1のコールバック関数となるということでしょうか?
    valueはuidを受け取るという理解でいいでしょうか?

    キャンセル

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

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

関連した質問

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

  • Node.js

    1488questions

    Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

  • 非同期処理

    86questions

    非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。