変奏現実

パソコンやMMORPGのことなどを思いつくまま・・・記載されている会社名・製品名・システム名などは、各社の商標、または登録商標です。

この画面は、簡易表示です

Promise

「javascript」postMessageとPromise

デスクトップに貼ったJSからSJISのCSVをダウンロードさせてみる【その後】の中で、
iframeへ送信したメッセージの応答をawaitで待機するようにしていますが、postMessageは本当にメッセージをポストするダケの機能なのでレス待ちなんで事ができません。

        displayStatus(`サンプルを${encoding}に変換中`);
        let unicode16CSV = await getSampleTextInnerText();

タネを明かすと、これを実現するために外部変数messageResponseStackにPromiseのresolveを保持し、どこかで応答メッセージを受信したらresolveを呼び出してawaitの待機状態を解除してもらっています。

つまり職場(プロセス)に居れば誰でも見れるホワイトボード(外部変数)に取引先(requestID)と連絡先(resolve)を書いてあるので、暇になった人が気が付けばかかってきた電話の相手先(requestID)の要件(event.data.message)を連絡してもらえる(ハズ

な仕組みです。

ついでにrequestIDはワンタイムなIDなのでホワイトボードから消してます。

/**
 * iframeのサンプルを取得する
 * @returns 
 */
const getSampleTextInnerText = () => {
    return new Promise((resolve, reject) => {
        loaderOpen();
        var us = getUniqueStr();
        messageResponseStack[us] = { 'resolve': resolve, 'reject': reject };
        myPostMessage('iframe', "getSampleText", "", us);
    });
};
    messageProcSetup({ ☚ここでmessageListner の応答メッセージタイプ毎のコールバック先をリストアップ
      ・・・
        'getEncodingListResponse': messageResponseProc,
      ・・・
    });
/**
 * メッセージのレスポンス処理 ☚ messageListnerからコールバックされる。
 */
const messageResponseStack = {};
const messageResponseProc = (data) => {
    loaderClose();
    if (data.requestId !== undefined) {
        messageResponseStack[data.requestId].resolve(JSON.parse(data.message));
        delete messageResponseStack[data.requestId];
    } else {
        alert(`not requestId${CRLF}data.type: ${data.type}`);
    }
};
/**
 * メッセージを受信する
 * @param {MessageEvent} event 
 */
const messageListner = (event) => {
    if (event.origin !== myOriginListener || event.type !== 'message') return;
    let data = event.data;
    let messageInfo = messageInfoList[data.type];
    if (messageInfo === undefined) {
        alert(`unkonwn message type = '${data.type}'${CRLF}message: ${data.message}`);
        return;
    }
    if (messageInfo !== undefined && messageInfo !== null) {
        if (Array.isArray(messageInfo)) {
            if (messageInfo.length > 1) {
                messageInfo[0](data, ...(messageInfo.slice(1)));
            } else {
                messageInfo[0](data);
            }
        } else {
            messageInfo(data);
        }
    }
};
/**
 * メッセージを送信する
 * @param {string} textType 
 * @param {string} textMessage 
 * @param {string} textRequestId
 */
const myPostMessage = (to, textType, textMessage, textRequestId) => {
    switch (to) {
        case 'parent':  //  親ドキュメントに送信する
            to = window.parent;
            break;
        case 'iframe': //   iframeへメッセージを送信する
            to = document.querySelector("iframe").contentWindow;
            break;
    }
    to.postMessage({
        type: textType,
        message: textMessage,
        requestId: textRequestId,
    }, myOriginSender);
};

もっといい方法があったらいいなぁ。



[JavaScript]Promise

Promiseオブジェクトの作り方には大きく2つある。

  1. new Promise( )
  2. 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チェインにすると・・・

壊れます。(痛すぎ




top