Promiseオブジェクトの作り方には大きく2つある。
- new Promise( )
- Promise.resolve( )
new Promise( ) は、新たにオブジェクトを作るけど、Promise.resolve( ) の方はstaticなPromiseオブジェクトを使いまわししているだろう。
class Promise {
constractor () {
・・・
}
static staticResolve = new Promise().resolve(undefined);
resolve() {
if (typeof(this) === "undefined") {
return Promise.staticResolve;
・・・
}
}
と考えると、Promise.resolve( ) はpromise状態が履行 (fulfilled)に確定しているところから始まるから
Promise.resolve().then( (a)=>{
/* なんたら */
}).then( (b)=>{
/* かんたら */
}).then( (c)=>{
/* それから */
}).then( (d)=>{
/* こうして */
}).then( (e)=>{
/* こうなった */
}).catch((e)=>{
/* どうして、そうなった? */
});
と書いても、「なんたら」部の最後まで進めば、直ぐに「かんたら」に突入し、ストレートに「こうなった」まで進んで一気に進むので、完全な同期処理である。
単に、try…catch…finally なんて美しくないという人向けでしかない。
ただ、ドコが壊れているのかイミフなコードを切り分けた状況のエビデンスを作る時には非常に便利である。
実際には<% try { %><% throw(“ココか?”); %> <%} catch(e) {consol.log(e); } %>式に
プローブ針を垂らすけど、エビデンスとしては判りにくいからね。
強いて言えば、「なんたら」「かんたら」「それから」「こうして」「こうなった」の全てにawaitで強制的にワーカースレッドを停止させる箇所が無いと意味が無いのだ。
そんな訳で、非同期で処理したいものを1つづつ、「非同期化」するしかないが、そう難しくはない。
class hoge {
// 非同期型 同期型と同じ数だけ作成する
asyncFunc(params) {
return new Promise((resolve, reject) => {
try {
let result = syncFunc(params);
resolve(result);
} catch(e) {
reject(e);
}
});
}
// 同期型
syncFunc(params) {
return foo;
}
・・・
}
と非同期呼び出し口を用意するだけ、
しかし、async接頭子のメソッドがいっぱい増えるのが嫌なら
class hoge {
// 非同期型 1つだけ作って使いまわし。※毎回 new Promise しなくていいのが嬉しい。
asyncCall(syncFunc,params) {
return new Promise((resolve, reject) => {
try {
let result = syncFunc(params);
resolve(result);
} catch(e) {
reject(e);
}
});
}
// 同期型
syncFunc1(params) {
return foo;
}
・・・
// 同期型
syncFuncN(params) {
return foo;
}
}
と、非同期化メソッドを用意しておくといいだろう。
引き渡すパラメータはthenチェインする前に確定してしまうから、後々トラブりそうなのが難点で、
しかも、下のコードを見て判るように、各処理に引き渡すパラメータは事前に用意することになるけど
paramを変数宣言しておいて使用すれば、各処理が動き出す前に調整は可能だし、何よりデバッグが楽だ。
ちゃんとデバッグが済んでから、試作型に実処理を埋め込めばいいだろう。
とりあえず、
// 非同期処理を準備する
let param1;
let param2;
let param3;
let param4;
let param5;
let promFunc1 = asyncFunc1(param1);
let promFunc2 = asyncFunc2(param2);
let promFunc3 = asyncFunc3(param3);
let promFunc4 = asyncFunc4(param4);
let promFunc5 = asyncFunc5(param5);
//
promFunc1.then(()=>{ /* なんたら param2を最新化 */
promFunc2.then(()=>{ /* かんたら param3を最新化 */
promFunc3.then(()=>{ /* それから param4を最新化 */
promFunc4().then(()=>{ /* こうして param5を最新化 */
promFunc5().then(()=>{ /* こうなった */
});});});});}).catch((e)=>{ /* どうして、そうなった? */
});
とコードすることで完全に非同期になると思う。
でも、thenチェインって云われているものは普通コレではない。
//同期っぽい書いてある非同期処理 ※パラメータは暗黙の了解で繋がっている
promFunc1(a) /* なんたら 暗黙のparam2を最新化 */
.then(promFunc2) /* かんたら 暗黙のparam3を最新化 */
.then(promFunc3) /* それから 暗黙のparam4を最新化 */
.then(promFunc4) /* こうして 暗黙のparam5を最新化 */
.then(promFunc5) /* こうなった */
.catch((e)=>{
/* どうして、そうなった? */
});
あるいは
//同期っぽく書いてある非同期処理
promFunc1(a).then( (b)=>{ /* なんたら */
let b = await promFunc2(b); /* かんたら */
return b;
}).then( (b)=>{
let c = await promFunc3(b); /* それから */
return c;
}).then( (c)=>{
let d = await promFunc4(c); /* こうして */
return d;
}).then( (d)=>{
let e = await promFunc5(d); /* こうなった */
return e;
}).catch((e)=>{
/* どうして、そうなった? */
});
のハズだ。この書き方の最大の問題点はawaitが本当に動作してくれるかどうかだ。
awaitはドコかのPromiseオブジェクトが「履行 (fulfilled): 処理が成功して完了」になるのを待つせいで、
ワーカースレッド内で同時に使用できるのはたったの1回だけ。
ネストできないから、上のコードを参照する処理や各async内でawaitしてるとawaitした途端にエラってしまう。
ので、
awaitを使ってエレガントなコードに酔いしれることができるのは「お一人様」つまり「貴族様仕様」なのだ。
つまり、awaitのaは、「とあるドコかの何か」ではなく「お一人様」の意味なのだ。
※試験には出ないけど重要です。
流石、文化が違うよね。ボクらはネストしてるthenチェーンか綺麗になおしたらデバッグ困難なコードに立ち向かう事になる。
だから、「ボタンをクリック」した直後に一回くらいに限定した方がいい。
サーバーと通信する際に使うと、画面隅に通知機能を搭載した途端にケンカ沙汰になってしまう。
というのが、ググった結果から得られた感想。
本当にこうなっているのかコードしてみると予想が結構ハズれているような気がする。
何せ、数年経てば、仕様書上は全く変わらないのに、全く違う動作になっている世界だけに。
実際、new Promiseのコールバックは今では即実行されてしまうので、
Promiseのthenの設定を全て終えメインスレッドが完了してから動いてくれると思ったら大間違い。
Promise.thenのコードを見つけると即実行するので、全く非同期ではない。
正確には、new Promiseのコールバックの中だけ、resolveかrejectを呼ぶまで非同期になってるダケで
Promise.resolveは、非同期に見える振りをしているだけ。
非同期にしたいなら
//同期っぽい書いてある非同期処理 ※パラメータは暗黙の了解で繋がっている
promFunc1(a) /* なんたら 暗黙のparam2を最新化 */
.then(promFunc2) /* かんたら 暗黙のparam3を最新化 */
.then(promFunc3) /* それから 暗黙のparam4を最新化 */
.then(promFunc4) /* こうして 暗黙のparam5を最新化 */
.then(promFunc5) /* こうなった */
.catch((e)=>{
/* どうして、そうなった? */
});
の書き方以外は意味が無い。
正解を引き当てたると後々面倒なことになるのはいつもの事。(大笑
「非同期な処理」≒「後でやって欲しい処理」を書くには、
各promFuncNの中でsetTimeoutしてresolveする以外に方法は無い。
ps.2021/12/13
サンプル追加。
thenチェインする処理を関数化すると、returnした値が引き継がれないのは痛いね。
thenチェインでreturnした値は、thisにぶら下げて引き渡しているんだろう。
1つの関数の中で閉じ籠って then チェイン して
うまく動いてるけど、ゴチャゴチャしているので
リファクタリングして美しいthenチェインにすると・・・
壊れます。(痛すぎ