回答編集履歴

7

最終更新

2018/04/11 08:59

投稿

miyabi-sun
miyabi-sun

スコア21158

test CHANGED
@@ -66,7 +66,15 @@
66
66
 
67
67
  まずコールバック引数というのは1つ以上の引数を必ず参照する作りにしてください。
68
68
 
69
- 中身でエラーが出てもtry~catchで取る事が出来ないので、第一引数はかならずerrになるからです。
69
+ 中身でエラーが出てもtry~catchで取る事が出来ないので、
70
+
71
+ 非同期処理を司るライブラリなんかは全て第一引数はかならずerrにするというルールがあるからです。
72
+
73
+
74
+
75
+ もし後続の処理を行われたらまずいという場合は、
76
+
77
+ `new Error('エラー理由')`でエラーを作成して第一引数に設定してください。
70
78
 
71
79
 
72
80
 
@@ -96,22 +104,26 @@
96
104
 
97
105
 
98
106
 
99
- // コールバック関数は必ず第一引数をerrにしてください。
107
+ // コールバック関数は必ず第一引数をerrにする
100
108
 
101
- // objとnewObjが使いたいと思ってそうでしたので両方要求する関数にしました。
109
+ // objとnewObjが使いたそうでしたので両方要求する関数に
102
110
 
103
111
  function asyncCallback (err, obj, newObj) {
104
112
 
105
113
  if (err) console.error(err);
106
114
 
115
+ // 本命の処理を行う
116
+
117
+ console.log(obj);
118
+
107
- console.log(err, newObj);
119
+ console.log(newObj);
108
120
 
109
121
  }
110
122
 
111
123
 
112
124
 
113
- const obj = {};
125
+ // objもnewObjもここで宣言しない方が良いので削除
114
126
 
115
- const newObj = makeNewObj(obj, asyncCallback);
127
+ makeNewObj({}, asyncCallback);
116
128
 
117
129
  ```

6

再調整

2018/04/11 08:59

投稿

miyabi-sun
miyabi-sun

スコア21158

test CHANGED
@@ -64,7 +64,15 @@
64
64
 
65
65
 
66
66
 
67
- コールバック引数というのは1つ以上の引数を必ず参照する作りにしてください。
67
+ まずコールバック引数というのは1つ以上の引数を必ず参照する作りにしてください。
68
+
69
+ 中身でエラーが出てもtry~catchで取る事が出来ないので、第一引数はかならずerrになるからです。
70
+
71
+
72
+
73
+ そして第二引数以降に、コールバック関数が使いたいと考えている変数を全て引数としてぶち込んで下さい。
74
+
75
+ これはnewObjをasyncCallbackで使えるようにした例です。
68
76
 
69
77
 
70
78
 
@@ -80,7 +88,7 @@
80
88
 
81
89
  // コールバックに値を引き継ぎたいので関数適用時の引数として渡す
82
90
 
83
- callback(null, {new: true});
91
+ callback(null, obj, {new: true});
84
92
 
85
93
  }, 0);
86
94
 
@@ -90,11 +98,13 @@
90
98
 
91
99
  // コールバック関数は必ず第一引数をerrにしてください。
92
100
 
93
- // if: もしobj使いたければ引数を3つ取る関数にする
101
+ // objとnewObjが使いたいと思ってそうでしたので両方要求する関数にしました。
94
102
 
95
- function asyncCallback (err, newObj) {
103
+ function asyncCallback (err, obj, newObj) {
96
104
 
105
+ if (err) console.error(err);
106
+
97
- console.log(err, newObj)
107
+ console.log(err, newObj);
98
108
 
99
109
  }
100
110
 

5

returnを削除出来て居なかったので再修正

2018/04/11 08:51

投稿

miyabi-sun
miyabi-sun

スコア21158

test CHANGED
@@ -84,19 +84,17 @@
84
84
 
85
85
  }, 0);
86
86
 
87
- // 新しいオブジェクトを返します。
88
-
89
- return {new: true};
90
-
91
87
  }
92
88
 
93
89
 
94
90
 
95
91
  // コールバック関数は必ず第一引数をerrにしてください。
96
92
 
97
- function asyncCallback (err, value) {
93
+ // if: もしobjも使いたければ引数を3つ取る関数にする
98
94
 
95
+ function asyncCallback (err, newObj) {
96
+
99
- console.log(err, value)
97
+ console.log(err, newObj)
100
98
 
101
99
  }
102
100
 

4

全面的に書き直し

2018/04/11 08:41

投稿

miyabi-sun
miyabi-sun

スコア21158

test CHANGED
@@ -1,32 +1,90 @@
1
+ > 以下はそのコードを質問用に簡略化したコードなのですが、関数式の代入先オブジェクトを関数に渡すコールバック関数の中で参照出来るのは、どういう仕組みからなのでしょうか?(質問1)
2
+
3
+
4
+
1
- そもそも、質問文のそれはコールバック関数適用出来ていません
5
+ コールバック関数適用と戻り値は完全に異なるものです
6
+
7
+ 非同期処理でコールバック関数を渡したからといって、戻り値を返してはならないというルールは存在しません。
8
+
9
+
10
+
11
+ setTimeoutなんかはこの両者の違いを上手に使っている典型例の一つで、
12
+
13
+ 一度コールバック関数で遅延させると同時に、IDが戻り値として返ってきます。
14
+
15
+ やっぱり実行を辞めたい場合はキャンセル関数に渡して上げる事で実行を取り下げる事が可能です。
2
16
 
3
17
 
4
18
 
5
19
  ```JavaScript
6
20
 
7
- function otherLibrary (callback) {
21
+ var id = setTimeout(() => console.log('test'), 10000);
8
22
 
9
- // 非同期コーバック
23
+ // 異常を感じたのでやっぱりキャンセだ!
10
24
 
11
- // ネットワークやI/O通信が終わり次第、イベントループに設定されて下記のコードが実行される
25
+ clearTimeout(id);
12
26
 
13
- // まぁ今回は横着してコンマ0秒で通信完了したことにしてprocess.nextTickを利用
27
+ // test <- キャンセルされたので文字は表示されない
14
28
 
15
- process.nextTick(() => callback(err, value));
16
-
17
- }
29
+ ```
18
30
 
19
31
 
32
+
33
+ > とりあえず僕的にはこういうことが可能らしいということはわかったのですが、直感的ではないと感じていて、これは良い作法なのかどうかも気になります。(質問2)
34
+
35
+
36
+
37
+ setTimeoutのように意味があれば良い作法ですし、
38
+
39
+ 意味が無ければ辞めたほうが良いでしょう。
40
+
41
+
42
+
43
+ [UNIX哲学](https://ja.wikipedia.org/wiki/UNIX%E5%93%B2%E5%AD%A6)に「一つのことを行い、またそれをうまくやるプログラムを書け。」とあります。
44
+
45
+ あまり色々な機能をゴテゴテと付け足した関数は汎用性が失われ使い勝手が悪くなります。
46
+
47
+
48
+
49
+ これらの事から、setTimeoutのようにキャンセルすることを見越してキー情報を返す。
50
+
51
+ …くらいの使い方以外はすることないんじゃないですかね?
52
+
53
+
54
+
55
+ > もし、これが良い作法ではない場合、このようにコールバックの中で代入先を参照したいような場合に、どのような方法が取れるのかを教えて頂けると嬉しいです。(質問3)
56
+
57
+
58
+
59
+ ???
60
+
61
+ ああ、なるほど…
62
+
63
+ やっぱり使い方分かってないじゃないですか。
64
+
65
+
66
+
67
+ コールバック引数というのは1つ以上の引数を必ず参照する作りにしてください。
68
+
69
+
70
+
71
+ ```JavaScript
20
72
 
21
73
  function makeNewObj(obj, callback) {
22
74
 
23
75
  // いろいろな処理がこの辺に
24
76
 
25
- otherLibrary(callback);
77
+ // 非同期コールバックを表現するためにsetTimeoutを利用
26
78
 
79
+ setTimeout(function() {
27
80
 
81
+ // コールバックに値を引き継ぎたいので関数適用時の引数として渡す
28
82
 
83
+ callback(null, {new: true});
84
+
85
+ }, 0);
86
+
29
- // つただのオブジェクト
87
+ // 新しいオブジェクトを返します。
30
88
 
31
89
  return {new: true};
32
90
 
@@ -34,132 +92,18 @@
34
92
 
35
93
 
36
94
 
95
+ // コールバック関数は必ず第一引数をerrにしてください。
96
+
37
97
  function asyncCallback (err, value) {
38
98
 
39
- // err, valueはこの関数内でアクセスすることになる
40
-
41
- console.log(err, value);
99
+ console.log(err, value)
42
100
 
43
101
  }
44
102
 
45
103
 
46
104
 
105
+ const obj = {};
106
+
47
107
  const newObj = makeNewObj(obj, asyncCallback);
48
108
 
49
- // これはあくまでmakeNewObjectの戻り値
50
-
51
- // errやvalueにはどう足掻いてもたどり着けない。
52
-
53
- // { new: true }
54
-
55
109
  ```
56
-
57
-
58
-
59
- こんな関係になっています。
60
-
61
- 従って、ライブラリの先では「はいはい実行しておくよ、そのまま次に行ってね」と通信を開始するだけです。
62
-
63
-
64
-
65
- なので、上記のerrやvalueを同期的なコードで受け取る事は絶対不可能です。
66
-
67
- (ES2017のasync / awaitを活用すれば見てくれだけは同期的に出来ますけどね)
68
-
69
-
70
-
71
- ---
72
-
73
-
74
-
75
- コールバック関数というのは、子供のお使いみたいなものです。
76
-
77
- では、朝子供が学校にいったママの行動を例としながら説明していきましょう。
78
-
79
-
80
-
81
- まず朝子供に朝食を食べさせ学校に向かわせます。
82
-
83
- ここまで子供は自宅に居るのでコントロール可能です(同期処理)
84
-
85
- ママは子供を学校に送る関数を叩き、子供は自分の手元を離れて学校へ移動しはじめました。
86
-
87
-
88
-
89
- 子供が学校に行っている間に、ママは書き置きを作ります。
90
-
91
- 「学校から帰ってきたら、プリントを机に出す、手を洗う、冷蔵庫のプリンを食べる」
92
-
93
-
94
-
95
- 子供が学校から返ってきたら、その書き置きを読み、
96
-
97
- 自発的にプリント机に出し、手を洗って、冷蔵庫のプリンを取り出し始めます。
98
-
99
-
100
-
101
- この書き置きがJavaScriptにおけるコールバック関数なのです。
102
-
103
- 子供が学校に行っている間、ママは学校で何があったかを把握することが出来ません。
104
-
105
- なので、学校へ送り出す関数の戻り値で、先生が渡したプリントを取得することは出来ません。
106
-
107
-
108
-
109
- このイメージをコードにあらわしてみました。
110
-
111
-
112
-
113
- ```JavaScript
114
-
115
- function toSchool (child, kakioki) {
116
-
117
- setTimeout(function () {
118
-
119
- var print = '今日の学級通信';
120
-
121
- kakioki(null, print);
122
-
123
- }, 8 * 60 * 60 * 1000);
124
-
125
- return child + 'が登校しました';
126
-
127
- }
128
-
129
-
130
-
131
- var child = '太郎';
132
-
133
- var result = toSchool(child, function (err, print) {
134
-
135
- // 慣習としてコールバック関数の第一引数はエラー
136
-
137
- if (err) {
138
-
139
- // 異常あり!子供が返ってこない!?電話する等の対応
140
-
141
- throw err;
142
-
143
- }
144
-
145
-
146
-
147
- // 以降は書き置きの内容
148
-
149
- console.log('プリントを机の上に出す');
150
-
151
- console.log(print); // 今日の学級通信
152
-
153
- console.log('手洗い');
154
-
155
- console.log('冷蔵庫のプリンを食べる');
156
-
157
- });
158
-
159
-
160
-
161
- console.log(result);
162
-
163
- // 太郎が登校しました
164
-
165
- ```

3

コードをもうちょっと洗練

2018/04/11 08:38

投稿

miyabi-sun
miyabi-sun

スコア21158

test CHANGED
@@ -128,23 +128,35 @@
128
128
 
129
129
 
130
130
 
131
- // 約8時間経った後に実行
131
+ var child = '太郎';
132
132
 
133
- function kakioki (err, print) {
133
+ var result = toSchool(child, function (err, print) {
134
134
 
135
- if (err) throw err; // 異常あり!子供が返っこない!?電話する等対応
135
+ // 慣習としコールバック関数第一引数はエラー
136
136
 
137
- console.log(print);
137
+ if (err) {
138
138
 
139
- // 今日学級通信
139
+ // 異常あり!子供が返ってこない!?電話する等対応
140
140
 
141
+ throw err;
142
+
141
- }
143
+ }
142
144
 
143
145
 
144
146
 
145
- var child = '太郎';
147
+ // 以降は書き置きの内容
146
148
 
147
- var result = toSchool(child, kakioki);
149
+ console.log('プリントを机の上に出す');
150
+
151
+ console.log(print); // 今日の学級通信
152
+
153
+ console.log('手洗い');
154
+
155
+ console.log('冷蔵庫のプリンを食べる');
156
+
157
+ });
158
+
159
+
148
160
 
149
161
  console.log(result);
150
162
 

2

関係をもう少し詳しく

2018/04/11 08:24

投稿

miyabi-sun
miyabi-sun

スコア21158

test CHANGED
@@ -103,3 +103,51 @@
103
103
  子供が学校に行っている間、ママは学校で何があったかを把握することが出来ません。
104
104
 
105
105
  なので、学校へ送り出す関数の戻り値で、先生が渡したプリントを取得することは出来ません。
106
+
107
+
108
+
109
+ このイメージをコードにあらわしてみました。
110
+
111
+
112
+
113
+ ```JavaScript
114
+
115
+ function toSchool (child, kakioki) {
116
+
117
+ setTimeout(function () {
118
+
119
+ var print = '今日の学級通信';
120
+
121
+ kakioki(null, print);
122
+
123
+ }, 8 * 60 * 60 * 1000);
124
+
125
+ return child + 'が登校しました';
126
+
127
+ }
128
+
129
+
130
+
131
+ // 約8時間経った後に実行
132
+
133
+ function kakioki (err, print) {
134
+
135
+ if (err) throw err; // 異常あり!子供が返ってこない!?電話する等の対応
136
+
137
+ console.log(print);
138
+
139
+ // 今日の学級通信
140
+
141
+ }
142
+
143
+
144
+
145
+ var child = '太郎';
146
+
147
+ var result = toSchool(child, kakioki);
148
+
149
+ console.log(result);
150
+
151
+ // 太郎が登校しました
152
+
153
+ ```

1

process.nextTickを追加

2018/04/11 08:20

投稿

miyabi-sun
miyabi-sun

スコア21158

test CHANGED
@@ -10,7 +10,9 @@
10
10
 
11
11
  // ネットワークやI/O通信が終わり次第、イベントループに設定されて下記のコードが実行される
12
12
 
13
+ // まぁ今回は横着してコンマ0秒で通信完了したことにしてprocess.nextTickを利用
14
+
13
- callback(err, value);
15
+ process.nextTick(() => callback(err, value));
14
16
 
15
17
  }
16
18