変奏現実

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

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

連鎖

[javascript]async await 連鎖

  • ドコかで同期を取るために awaitする
    • awaitを使用するためにその関数にasyncを付ける。
      • その関数を呼び出す関数も概ね後処理があるのでawaitする
        • awaitを使用するためにその関数にasyncを付ける。
          • …延々と連鎖

セルに表示するデータをindexedDBで取得するように修正したら・・・↑の様なことになった。

サクラエディタに関数とソレを参照する関数を階層リストを書いて漏れチェック。

幸いにもコノ程度で済んだ。

  • spreadSheet:getCellData
    • layout:createThTdElement
      • layout:createTrElement
        • layout:createTableElement
      • layout:createColThTdElement
        • layout:insertOverCol
          • command:moveCell
            • event:clickMouseEvent
            • layout:makePageContents
              • spreadSheet:makePageContents
    • layout:getCellData
      • command:openEntry
      • layout:isCellData
        • command:chkDirectionData
  • spreadSheet:setCellData
    • layout:setCellData
      • command.closeEntry
        • event:dblclickMouseEvent

そこまでは良かったが動作が変。

地道にチェックしていくと、Arrayのsomeのコールバックがasyncな場合でも同期を取らず、どんどん処理を進めていくコトが判明。

対処方法としては new PromiseでPromiseの配列を作ってPromise.allで完了待ちが筋だが、

const objRow = createHtmlElement('tr', attrs);
let w = 0;
if (nRow === 0) {//初期表示時   ※TODO 初期表示時は左上セルがA1と仮定
    const cols = this.spreadSheet.cols;
    for (let nCol = 0; nCol <= cols; nCol++) {
        const cell = await this.createThTdElement(objRow, nRow, nCol);
        w += cell.clientWidth;
        if (rectParent.width < w) {
            break;
        }
    }
}

外の変数を使っている場合が多々あったのでsome化したものをfor文に戻した。

rangeをPromise対応してみようか・・・

/**
 *  連番配列っぽいイテレータっぽいものを作成する
 * @param {integer} start           開始番号
 * @param {integer} end             終了番号
 * @returns {iterator}              開始番号から終了番号を格納した配列っぽいイテレータ
 */
const range = (start, end) => {
    let step = 1

    const rangeIterator = {
        some: function (func) {//someっぽく動作させる
            for (let cnt = start; cnt <= end; cnt += step) {
                let rc = func(cnt);
                if (rc) {
                    return true;
                }
            }
            return false;
        },
        map: function (func) {//mapっぽく動作させる
            const f = false;
            let aRc;
            if (f) {
                aRc = new Array(end - start + 1);
            }
            let index = 0;
            for (let cnt = start; cnt <= end; cnt += step) {
                const rc = func(cnt);
                if (aRc) aRc[index++] = rc;
            }
            return aRc;
        },
    };
    return rangeIterator;
}

無理っぽいなぁ~

/**
 *  連番配列っぽいイテレータっぽいものを作成する
 * @param {integer} start           開始番号
 * @param {integer} end             終了番号
 * @param {integer} step            ステップ
 * @returns {iterator}              開始番号から終了番号を格納した配列っぽいイテレータ
 */
const promiseRange = (start, end, step = 1) => {
    return rangeIterator = {
        some: function (func) {//someっぽく動作させる
            return new Promise(async (resolve, reject) => {
                for (let cnt = start; cnt <= end; cnt += step) {
                    let rc = await func(cnt);
                    if (rc) {
                        resolve(true);
                        return;
                    }
                }
                resolve(false);
            });
        },
        map: function (func) {//mapっぽく動作させる
            return new Promise(async (resolve, reject) => {
                let aRc = [];
                for (let cnt = start; cnt <= end; cnt += step) {
                    const rc = await func(cnt);
                    aRc.push(rc);
                }
                resolve(aRc);
            });
        },
    };
}

こんな風に使うんだけど

const cols = this.spreadSheet.cols;
await promiseRange(0, cols).some(async (nCol) => {
    const cell = await this.createThTdElement(objRow, nRow, nCol);
    w += cell.clientWidth;
    if (rectParent.width < w) {
        return true;
    }
    return false;
});

動くから良し。

ps.

だがしかし、後でasync付きのメソッドを呼び出す時にawaitし忘れガチ。なので、メソッド名のツールチップを見て戻り値がPromiseになってないか確認しないと同期ズレで酷い目に遭う。asyncは明示しないとスクリプト側が大変だと思うけど、awaitは暗黙の了解ぽく自動的に処理して欲しいなぁ。代わりに noWait実装でヨロ。

ps.2024/3/18

計算式にセル参照を追加したら、またasync/awaitの連鎖。しかし全般的にパーサやコールバックをasyncし、呼び出し側でawait するダケで済む。かと思ったが、Array.reduceでPromiseを回すと、パーサーコンピネータなので、

Array.reduce
  return new Promise((resolve,reject)=>{
 promise,then(()=>{
   ・・・
  Array.reduce
    return new Promise((resolve,reject)=>{
     promise,then(()=>{
     ・・・
    Array.reduce
       return new Promise((resolve,reject)=>{
        promise,then(()=>{
       ・・・
         resolve(xxx);

と各所(chiceやseq等で)階層ループになってしまう。突入するのはいいが、離脱する際に、どのArray.reduceに戻るか不安定。function () {…}.bind(this)のbindのthisとPromise.resolve(…)は仕様的にも相性が最悪だけど、reduceか階層ループのいづれかが無ければ、どうと云うことは無いので、Array.reduce(…)をfor(){…}に戻した。

その後、セル参照のある数式を連鎖して再計算させてみたら、indexedDBのトランザクションの階層的な利用は不可だったので、階層化しそうな処理をsetTimeoutでばらまいたが、トランザクションが終わらないうちに動き出すので、一旦配列に入れトランザクション完了後に、配列をpopで読出しつつsetTimeoutでばらまいた。そうするしかない。順序を守りつつ非同期で処理しないとトランザクションが階層化してしまうからね。




top