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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Node.js

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

非同期処理

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

Q&A

解決済

2回答

903閲覧

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

MOTOMUR

総合スコア195

Node.js

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

非同期処理

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

1グッド

9クリップ

投稿2018/01/14 11:50

編集2018/01/14 12:53

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

自分の理想的な使い方。

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

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

nodejs

1var p1 = new promise(function(resolve,reject){ 2 // any function 3}) 4var p2 = p1.then(function(value){ 5 // any function 6})

これで多分、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として変数をいざ扱おうと思った時に混乱してしまいました。なので質問させてください。

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

nodejs

1//there is root of file 2var outside = "this is out" 3any function(){ 4 var inside_any = "this is inside any" 5 function(){ 6 var most_inside = "Hi" 7 } 8 function(){ 9 console.log(outside + inside_any + most_inside) 10 //①はこの部分です。 11 } 12 13 var p1 = new promise(function(resolve){ 14 var inside_promise = "hello" 15 }) 16 var p2 = p1.then(function(resolve){ 17 console.log(inside_promise) 18 //②はこの部分です。 19 }) 20 21 console.log(inside_any + most_inside + inside_promise) 22 //③はこの部分です。 23}

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

DrqYuto👍を押しています

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

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

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

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

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

JavaScript

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

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

JavaScript

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

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

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

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

javaScript

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

これが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 06:42

編集2018/01/15 06:52
miyabi-sun

総合スコア21158

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

MOTOMUR

2018/01/15 12: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に値を代入するということだと思いました。
miyabi-sun

2018/01/15 13: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);  })
guest

0

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

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

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

投稿2018/01/14 12:23

maisumakun

総合スコア145183

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

MOTOMUR

2018/01/14 12:53

質問文のコード間違ってましたね。修正しておきます。 理解力不足かもしれないですが、確認させてください。 var p1 = new promise(function(resolve,reject){ var uid = "any"  resolve(uid) }) var p2 = p1.then(function(value){ } こう記述すると、p2はp1のコールバック関数となるということでしょうか? valueはuidを受け取るという理解でいいでしょうか?
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問