変奏現実

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

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

javascript

「JavaScript」連番の配列を作ってみる

どうでもいいけど、C言語っぽく

let result = new Array(10);
for (let index = 0; index < 10; index++) {
 result[index]= index + 1;
}
console.log(result);
// (10) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

では色々注意が必要。

から始まって・・・

let result = []
for (let index = 0; index < 10; index++) {
 result.push(index + 1);
}
console.log(result);
// (10) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

この時点でまだindexをいっぱい書いてあるから

index + 1じゃなくて let index=1すれば良い!

とか余計な修正をしだすと

index < 10の部分を見落としてそう。

つまり、indexの出現は少ない方が良い。

しかし、

var result = [...Array(10)].map((_, i) => i+1) 
console.log(result);
// (10) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

では作業用配列をいくつも作ってしまう。

Array(10)とか[…xxx]とかね。

とは云え

var result = Array.from({length: 10},(_, index) => index + 1);
console.log(result);
// (10) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

に至っては {length:10 } でObjectの振りというか {…}は紛れもなくObjectだけど、

length: 10とよくあるプロパティでArray.fromを騙すのはいいとして

indexが唐突に出てくる感じがするけど・・・

Array.from(arrayLike, mapFn)

Array.fromはこんなパラメータになっているので

Array.prototype.map((value, index) => { ... });

を思い浮かべば、理解してもらえそう。

それに無駄な作業用配列も作らないから

1から1万までの連番配列を作るには丁度良い。

気がする。

そこで

Array.from(arrayLike, mapFn, thisArg)

まで使ってみると

var result = Array.from({ length: 10 }, function (_, index) { return index + this.start; }, { start: 1 });
console.log(result);
// (10) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

明示的に連番の初期値を設定できて気持ちはすっきりするけど、

アロー関数にとってthisはwindowしかありえないので

無名functionにしないといけないし

スクロールバーが出るくらい長いので蛇足感がある。

だが、連番+αのテストデータを作るのには向いている気がするので次点とした。

結果は・・・

var result = Array.from({length: 10},(_, index) => index + 1);

が良さげ。

/**
 *  連番作成
 * @param {integer} start           開始番号
 * @param {integer} end             終了番号
 * @return {Array of object}        開始番号~終了番号の配列
 */
const range = (start, end) => {
    let rc = Array.from({ length: (end - start + 1) }, (_, index) => index + start);
    if (callbackMap) {
        rc = rc.map(callbackMap);
    }
    return rc;
};

range().map(callback) をラップしてrangeMapを作ってみたが、使い心地は・・・

range(1, rows).map((nRow) => {
    createTrElement(objTable, nRow, ...);
});

rangeMap(1, rows,(nRow) => {
    createTrElement(objTable, nRow, ...);
});

見分けが付かないくらい同じだった。(笑

ps.

クエスチョン:spreadSheetのコードで上のrangeを使ってみると妙に重い(初期表示2秒前後)。何でだろう?

アンサー:今のEXCELって最大1048576行16384列なのでこの最大値に合わてみたら、初期表示で物凄い量の配列を作っていた!普通のfor文に戻すとサクっと初期表示。配列のままでも動かない訳では無いけど、時々Chromeやタスクバーがマウスに反応しなかった原因のような気がする。

100万×1.6万≒160億 だからなぁ~

でもfor文のままなのも、配列じゃなくても連番指定でsomeとmapっぽく動作すればいいから

/**
 *  連番配列っぽいイテレータを作成する
 * @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) {
                if (func(cnt)) {
                    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;
}


「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);
};

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



デスクトップに貼ったJSからSJISのCSVをダウンロードさせてみる【その後】

あれから6年?(大笑

結局何が原因でうまくいかなかったのかと云えば

FileReader.readAsText(${Blob型データ},”SJIS”);に

未定義あるいはUTF16に変換できない文字を与えるとの大雑把には

  • シフトJISの下位コードのMSBが0の場合は、「0xfffd」「下位コード」の2バイトに変換。
  • シフトJISの下位コードのMSBが1の場合は、「0xfffd」の1バイトに変換。

している様に見えるが・・・

Blobに1文字づつ入れFileReaderで読むと

  • シフトJISの下位コードのMSBに関わらず、「0xfffd」の1バイトに変換。

してしまうので

Blobに複数の文字を入れると、

挙動が不安定になるらしい。

なので、

1文字づつシフトJISをUTF16に変換してシフトJIS変換表を作成するしかない様だ。

実際に動かしてみると、

1秒もかからず変換表を作ってしまうので

これでいいんじゃないかな?

まだ、黑(fc4f)付近で変換に失敗している???

あ、文字領域が拡張されていたw(6年の重みかな?

f040~fc7e、f080~fcfcを変換表に追加。

ついでに外部変数を止めPromiseでガガガと作らせようとしたら・・・

Promiseを100個以上作るとPromise.Allでうまく処理できない ※返事が返ってこない。

ので、awaitで順次処理に変えてみる。※asyncも付けまくる。

さらにダウンロードの直前に変換表を作り直してみると

3秒くらいかかってしまった。

変換表をjsファイルでダウンロードできるようにした方がいいかな?

サンプルをSJIS変換を使って作成してみた。

やはり処理時間が長めだな。

あれ?未定義な文字しかない行が結構ある!

カット!

ちゃんと処理時間を表示してみると長めだなぁ。

サンプルをiframe化しUTF16未対応文字は□に変えてみた。

さらに、EUCとかも対応できそうなのでやってみた。

サンプル

まだ、EUC-jpやiso-2022-jpでコードページを変更するコード(エスケープシーケンス等)が必要な文字は変換時に化けてしまう。

どんどん遅くなっていく。(キニシナイ

外部変数アクセスするのを減らそうとawaitしてみるとasync宣言付けたアロー関数だらけ。

Promise と resolve と await を良い塩梅で混ぜないといけないのも大変。

一番困ったのはVisualStudio Codeでデバッグしてみると

頻繁にPromise.allが暴走するが

それは非同期処理中はエラっても「デバッグコンソール」は裏に隠れたままなので、

SyntaxErrorがあっても気が付かないせい。

暫くしても処理が終わらないなぁ?と思い、

「問題」「出力」「デバッグコンソール」「ターミナル」タグをひっくり返してみると。

xxx変数は未定義です。

とか

xxxにmapプロパティは存在しません。

が見つかる。

でもデバッグを再開してもコンソールは初期化されないので、 2024/3/21現在はクリアされてる。

エラったらコンソールをクリアしてからデバッグしなおさないと泥沼。

postMessageでiframeとやり取りしているので、サンプルに入力した内容等がブラウザの他のタブにもpostされている可能性があります。

またサロゲートペア(𩸽:U+D867とU+DE3D、𠮷:U+D842とU+DFB7とか)にも対応していません。

※サロゲートペアは入力したいサロゲートペア文字の16進コードが判れば、IMEパッドの文字カデゴリの上位サロゲートD867の枠をクリックすると�っぽく表示されますが下位サロゲートからDE3Dの枠をクリックすると𩸽に変わります。

と云うのも、サロゲートペアはUTF16独自のものでSJIS文字に対応する文字の定義が無いっぽいです。SJIS変換表はSJISの文字領域に対して作成していますが行頭の””で囲まれた文字に上記のサロゲートペアが無いのです。または「部」から11画の魚辺の終わりの方にある𩸽を探し出すかですね。

JIS第4水準の第93区に𩸽が、AJ1ベース文字-JIS外に𠮷があるっぽいですが、ISO-2022-JPのザックリとしたエスケープシーケンスでは得られないっぽいです。

  • ASCII: 1b 28 42
  • JIS X 0208: 1b 24 42

一応、ISO-2022-JP-2004も追加してみたけどブラウザ未対応のencoding名らしく組み込んだエスケープ文字がそのまま出力され全角どころか半角すらちゃんと出なくなってしまう。

encodingx.jsのconst encodingInfoList をイジるとブラウザが対応している色々なencodingを試せます。

JISの様に文字の前にエスケープシーケンスが必要なものは配列の5~6番目に配列で記入できます。

  • 5番目は文字を全角等に切替るシーケンス
  • 6番目は半角に戻すシーケンス。

ここに新たなencodingを追加すればHTMLのselectの項目も自動的に増えます。

※まともに動作するかどうかは不明だけど

/**
 * エンコーディング情報
 *  {
 *      "encoding": {
 *          8:  [
 *                  [上位開始バイト,上位終了バイト,下位開始バイト,下位終了バイト,[接頭語...],[接尾語...]]
 *              ... 
 *          ],
 *          16: [
 *                  [上位開始バイト,上位終了バイト,下位開始バイト,下位終了バイト,[接頭語...],[接尾語...]]
 *              ...
 *          ],
 *      },
 *  ...
 *  }
 */
const encodingInfoList = {
    "windows-1252": {
        //  8ビット
        8: [
            [0x0000, 0x0000, 0x0000, 0x00ff],
        ],
    },
    "shift-jis": {
        //  8ビット
        8: [
            [0x0000, 0x0000, 0x0000, 0x00ff],
        ],
        //  16ビット
        16: [
            //  第1面
            [0x0081, 0x009f, 0x0040, 0x007e],
            [0x0081, 0x009f, 0x0080, 0x00fc],
            [0x00e0, 0x00ef, 0x0040, 0x007e],
            [0x00e0, 0x00ef, 0x0080, 0x00fc],
            //  第2面
            [0x00f0, 0x00fc, 0x0040, 0x007e],
            [0x00f0, 0x00fc, 0x0080, 0x00fc],
        ],
    },
    "iso-2022-jp": {
        8: [
            //  8ビット
            [0x0000, 0x0000, 0x0000, 0x00ff, [0x1B, 0x28, 0x42], []],
        ],
        16: [
            // JIS X 0208コード表   1面
            //
            // 記号、英数字、かな(01区~08区)
            [0x0021, 0x0028, 0x0020, 0x007f, [0x1B, 0x24, 0x42], [0x1B, 0x28, 0x42]],
            // 第1水準漢字(16区~47区)
            [0x0030, 0x004f, 0x0020, 0x007f, [0x1B, 0x24, 0x42], [0x1B, 0x28, 0x42]],
            // 第2水準漢字(48区~84区)
            [0x0050, 0x0073, 0x0020, 0x007f, [0x1B, 0x24, 0x42], [0x1B, 0x28, 0x42]],
            [0x0074, 0x0074, 0x0020, 0x002f, [0x1B, 0x24, 0x42], [0x1B, 0x28, 0x42]],
        ],
    },
    "iso-2022-jp-2004": {
        8: [
            //  ASCII
            [0x0000, 0x0000, 0x0000, 0x00ff, [0x1B, 0x28, 0x42], []],
        ],
        16: [
            // JIS X 0213漢字集合1面:コード表
            // 1面(JIS X 0208および第3水準漢字)(01区~94区)
            [0x0021, 0x007e, 0x0020, 0x007f, [0x1B, 0x24, 0x28, 0x51], [0x1B, 0x28, 0x42]],

            // JIS X 0213漢字集合2面:コード表
            // 2面(第4水準漢字)(01区~08区)
            [0x0021, 0x007e, 0x0020, 0x007f, [0x1B, 0x24, 0x28, 0x50], [0x1B, 0x28, 0x42]],
        ],
    },
    "euc-jp": {
        //  8ビット
        8: [
            [0x0000, 0x0000, 0x0000, 0x007f],
            [0x0000, 0x0000, 0x00a0, 0x00df, [0x8e], []],
        ],
        // JIS X 0208コード表   1面
        //
        // 記号、英数字、かな(01区~08区)
        16: [
            [1 + 0x0a0, 8 + 0x0a0, 0 + 0x0a0, 95 + 0x0a0],
            // 第1水準漢字(16区~47区)
            [16 + 0x0a0, 47 + 0x0a0, 0 + 0x0a0, 95 + 0x0a0],
            // 第2水準漢字(48区~84区)
            [48 + 0x0a0, 83 + 0x0a0, 0 + 0x0a0, 95 + 0x0a0],
            [84 + 0x0a0, 84 + 0x0a0, 0 + 0x0a0, 15 + 0x0a0],
        ],
    },
};

ここまでくるとブラウザのJavaScriptのreadAsTextのencodingのチェッカーっぽくなってきた。(笑

今更UTF-8以外のテキストデータをブラウザで使う意味は遠の昔に消えているので、

ExcelがUTF-8のCSVをちゃんと読める様になったら、もう存在意義が無いに等しい。(大笑

なので、あて名書き5をアップ。おっと2014年?2024年に修正っと(笑

なんてこった10年も経ってたのか!

ps.2024/2/13

readAsTextで1文字づつ処理するのはやはり遅い。文字間にNULエリアを挿入してみたら文字同士の影響が無い様なので、エスケープシーケンスも考慮して16バイト毎に文字を配置することで、データの下位バイトの範囲単位で(サンプルの場合は1行単位)で処理するように修正したら、1秒以内に処理を終える様にしたのでchromeで見ているなら【24時間以内の履歴を削除】するとサンプルの変換処理が妙に速くなる。(ハズ

全データの範囲を一度に処理すればもっと速くなるハズ。

ps.2025/4/10

@returnを追記すらのでハイライトの行番号も修正

ps.2025/4/19

encodingInfoListにbig5を追加してみた。運悪く2バイト目が0x7EでmakeSampleRowで不具合が起きたので修正。ファビコンを表示。<DOCTYPE html>になってるトコ多いな~アップロードした状況でbig5で【ダウンロード】押すとalert表示する 8: […] が無いせいなので修正

ps.2025/4/25

【変換表をダウンロード】するとエラっていたので修正

big5で下位バイトが「0x00a1」と1から始まるとダンプも1から始まってたので0から始まる様に修正

いつのまにか変換範囲が増加してる時に対応してる状況に対応するために

コードページ選択時にサンプルと一緒にソースのエンコード情報も表示させるようにした。

ココで必要な部分だけ残して【エンコード情報からサンプル表示】ボタンを押すと

エンコーディング情報で指定した範囲でサンプルを表示する。

いつのまにか「ブラウザが変換しない文字」を 表示になっていたのでを表示するようにした。

ps.2025/4/25その2

iframeの中は簡単には操作できないが

  const iframe = document.querySelector('iframe');
  const doc = iframe.contentWindow.document; // 👈この辺はコロコロ変わってる様なのでデバッガで要確認
  doc.open();
  doc.write(`AAAAABBBBBCCCC`);
  doc.close();

は表示できるけど、metaタグこみで

  const iframe = document.querySelector('iframe');
  const doc = iframe.contentWindow.document; // 👈この辺はコロコロ変わってる様なのでデバッガで要確認
  doc.open();
  doc.write(`<!DOCTYPE html>
<html lang="ja">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=sjis">
  <meta http-equiv="Cache-Control" content="no-cache">
  <title>UTF16をSJISとかに変換するサンプル5</title>
  <link href="encodingtest.css" media="all" rel="stylesheet" type="text/css" />
  <script src="encodinglib.js"></script>
  <script src="message.js"></script>
  <script src="encodingx.js"></script>
  <script src="download.js"></script>
  <script src="encodingsample.js"></script>
</head>
<body>
  <div class="sample" contenteditable id="sampleText"></div>
</body>
</html>`);
  doc.close();

はダメっぽく、偶然に動いた(?)時もcharsetはUTF-8のままだった。

iframeが非推奨ポクなってきたのでIFRAME廃止版。一応旧版は残しておく

当初の目標が遠くなったせいでガッカリしてたのか奇妙なバグが残る。

【ダウンロード】ボタンを押した後に【~サンプル表示】ボタンを押すとサンプルが■だらけになった。8ビットのコードは影響が無く、下位バイト分の変換表を作るトコロで上書きを繰り返していて「最後の下位バイト分ダケの変換表」の変換表になっていた。

入口が2通りあると陥るバグ。(あるある

ついでに doc.close(); の消し残しもあった。

うん。今日は散々だな。



[javascript]videoタグ

※大抵のページに動画のファイルをドラッグするとドラッグ処理が無効になっているから別タブで動画が再生される。

videoタグのサンプルを作成した。

何かの動画のファイルをページにドロップすると再生する。

しかし、サーバーに配置しているので、

ローカルの動画ファイルパスを指定してもアクセス不可なので

ファイルパスの指定を諦めてファイルからBlobデータを作ってsrcに設定すれば再生できた。

レイアウト構成をJSON形式で、Import Export できるようにしたのに・・・

[
  {
    "fileName": [
      "file:///${drive-1}:/${path-1}/${file-1}.${ext-1}",
      ...
      "file:///${drive-n}:/${path-n}/${file-n}.${ext-n}"
    ],
    "height": ${height},
    "width": ${width}
  },
  ...
]

複数の動画ファイルをドラッグすると、ページを大きくはみ出て再生しだすので、

【分割】ボタンで適当にページに収まりそうな感じにvideoタグをリサイズしてみた。

Excelのウインドウの整列っぽくするのは難しそう。



[javascript]createElementとかaddEventListenerはダルい

createElementはダルい。

そもそも、innerHTMLを使えば済む。

createElementはタグ名が付いてるダケの空っぽなHTMLエレメントを作るだけなので

var button = createElement('button');
button.className = 'fileButton fileDrag';
button.id = 'fileButton' + buttonId;
button.type = 'button';
button.innerHTML = caption;
parentObj.appendChild(button);

と・・・ダラダラと書かないといけないから

      var fileButton  = createElement('button', {
        className:  ['fileButton','fileDrag'],
        id:         'fileButton' + buttonId,
        type:       'button',
        innerHTML:  caption,
        parent:     parentObj
      });

の様に連想配列でエレメントの属性を一式で指定できればいいのに・・・

と思ったので書いてみた。

    //  HTMLエレメントを作成します
    const createElement = (tagName, options) => {
      var eventList = ['ended'];
      var elm  = document.createElement(tagName);
      Object.keys(options).forEach( (key) => {
        // eventの場合
        if( eventList.includes(key) ) {
          elm.addEventListener(key,options[key]);
        } else {
          // event以外の場合
          switch(key) {
            case 'className': // プロパティはclassNameですが、タグの属性はclassなので別途に処理します。
              if(Array.isArray(options[key])) {
                elm.classList.add(...options[key]); // 配列を展開しパラメータとして引き渡す
              } else {
                elm.classList.add(options[key]);
              }
              break;
            case 'innerHTML': // setAttributeではタグ内にinnerhtml="***"と属性を生成するので別途に処理します。
              elm.innerHTML = options[key];
              break;
            case 'parent':    // setAttributeではタグ内にparent="***"と属性を生成する様で、画面に生成したエレメントを割り付けないので別途に処理します。
              options[key].appendChild(elm);
              break;
            default:
              elm.setAttribute(key, options[key]);
              break;
          }
        }
      });
      return elm;
    };

イベントに対応するeventListの中身が少ないのは、creapteElementの後で毎回addEventListenerするのも面倒なので、スクリプトの最初でクラス指定でイベント登録を済ませたかったから・・・

とは云え、クラス数×イベント数がいっぱいあったら、その分のdocument.addEventListenerは呼び出したくないので、

    const onLoadWindow = () => {
      //  コントロールのイベント処理を登録します
      const classEvents = [
        /* onload */
        { className:  'fileButton',         events:   { 'click':  onClickFileButton}},
      ...
      ];
      registEventListener(classEvents);
    };
    //  documenイベント処理
    var documentEvents = {};
    const documentEventListener = (event) => {
      // 処理
      var target = event.target || event.srcElement;
      Array.from(target.classList).map((className) => documentEvents[event.type][className])
        .filter((func) => func !== undefined)
        .forEach((func) => func(event));
    };
    //  指定クラスを持つHTMLエレメントにイベントを登録します
    const registEventListener = (classEvents) => {
      // classEvents配列数分ループ
      classEvents.forEach((classEvent) => {
        // イベント数分ループ
        Object.keys(classEvent.events).forEach((eventName) => {
          // クラスのイベントごとの処理
          if( documentEvents[eventName] === undefined ) {
            // documentEvents[eventName][classEvent.className]に代入できるように初期化
            documentEvents[eventName] = {};
            document.addEventListener(eventName, documentEventListener);
          }
          // ※同じクラス・同じイベントに対応する処理は1つだけ
          documentEvents[eventName][classEvent.className] = classEvent.events[eventName];
        });
      });
    };
    //  画面初期表示時の処理
    window.onload = onLoadWindow;

イベントごとに1回document.addEventListenerを呼び出し、イベント発生時に呼出された時にイベントターゲットのクラス名からイベントで処理したい関数をリストアップして呼び出す様にしてみた。

const classEvents =[
  {
    className:  'fileButton',
    events:   {
      'click':  onClickFileButton}
    },
  },
  ...
]

この書き方は思いついたイベントを書き殴るには都合がいいけど、documentEventsでは扱いにくいので

var documentEvents = [
  {
    'click': [
      {
        'fileButton': onClickFileButton,
        ...
      }
    ],
  ...
  }
]

に置き換え、documentEvents[‘click’][‘fileButton’]からonClickFileButtonが容易に取得できるようにしている。

で・・・全部済ませたかったが、

videoタグのendedイベントはdocument.addEventListenerで拾えなかったので

自作のcreateElementで作成したHTMLエレメントにaddEventListenerでendedイベントを登録できるようにした。(ワケ

気分的には楽になるけど、

なぜかソースは長くなる。(大笑い



[javascript]matchとか

HTMLで同じようなタグが多いとのidに1とか2とか数値を付けるけど、

この数値の部分だけ欲しい時がある。

parseInt(id.match(/\d+$/))

とするとidの末尾の方の数字を数値で得られる。

matchは配列を返すので [0] を付けてparseIntに渡した方がいいけど、parseIntは引数を文字列に変換する際に配列なら先頭のみを対象とするみたいでこのままでも支障は無い。

ファイルの拡張子ダケ欲しい時

fileName.match(/([.])(.*$)/)[2]

[1]の様に思えるけど

matchはキャプチャグループを使うと

[0]:全体

[1]:1つ目のキャプチャグループ

[2]:2つ目のキャプチャグループ

の様に返すので ピリオドの後の拡張子部分ダケ欲しい場合は[2]になる。



[Javascript]漢字読み仮名辞書とローマ字変換

VBAではバリバリと日本語の変数を使うけど

JavaScriptでは日本語変数は使えないので、よく打ち間違えるからVisualStudioCodeのヘルプは大助かり

しかし、Excelで仕様書を書く時は何もヘルプ機能が無いのでAll JavaScriptで作ってみた。これならダウンロードすれば安心。(なハズ。

漢字読み仮名辞書とローマ字変換

  • ローマ字変換と漢字読み仮名変換はほぼ似てるのでクラス化
    • 対象テキストと辞書のエントリィ(ひらかなとか漢字とか)の両方の先頭部分を比較し一致箇所が一番長いものを拾い出し、辞書の読み(ローマ字とかひらがな)に差し替える。
    • 基本的に辞書はJSONのまま使用。
  • ローマ字変換と漢字ひら仮名変換の2オブジェクトを作成。
    • 辞書はそれぞれ別途用意。
    • 変数名で使うセパレータ(_)は、漢字読み仮名変換のみ指定。
    • 接頭語、接尾語指定は、漢字読み仮名変換のみ指定。
    • 例外的な記述の仕方はtralsrateAfterをオーバーロード。

使い方1:漢字の読みを登録しよう。

  1. 漢字欄に「新幹線」と入力
  2. ひらかな欄に「しんかんせん」と
  3. 【単語の読みを辞書に登録】ボタンを押す

使い方2:辞書に登録されたか確認しよう。

  1. 【辞書⇒クリップボード】ボタンを押す
  2. テキストエディタで貼り付け
  3. 多分、「新幹線\tしんかんせん\r\n」と出るハズ

使い方3:辞書を更新しよう

  1. テキストエディタを「新幹線\tしんかんせんwwww\r\n」に書き換える
  2. 全文をコピー
  3. 【クリップボード⇒辞書差替】ボタンを押す
  4. 漢字欄が「新幹線」なのを確認する
  5. 【漢字⇒ひらかな】ボタンを押す
  6. ひらがな欄に「しんかんせんwwww」と出るハズ

使い方4:ローマ字に変換しよう。

  1. ひらがな欄が「しんかんせんwwww」なのを確認する
  2. 【ひらがな⇒ローマ字】ボタンを押す
  3. ローマ字(スネークケース)欄に「shinkansenwww」と出るハズ
    • ※ローマ字変換処理は「知らない文字はそのまま出力する様になっている」

使い方5:辞書に登録した漢字を組み合わせて使ってみよう

  1. 漢字欄を「新幹線新幹線」と書き換える
  2. 【漢字⇒ひらかな】ボタンを押す
  3. ひらがな欄が「しんかんせん_しんかんせん」になる。
  4. 【ひらがな⇒ローマ字】ボタンを押す
  5. ローマ字(スネークケース)欄に「shinkansen_shinkansen」と出るハズ
    • ※登録した漢字と登録した漢字の間に_が入る。(仕様
  6. キャメルケース欄に「shinkansenShinkansen」と出るハズ
  7. コンスタントケース欄に「SHINKANSEN_SHINKANSEN」と出るハズ
  8. パスカルケース欄に「ShinkansenShinkansen」と出るハズ
  9. チェインケース欄に「shinkansen-shinkansen」と出るハズ

使い方6:「~1」とか「~2」とか、ありがちな変数名がどう変換されるかな?

  1. 漢字欄を「新幹線1」と書き換える
  2. 【漢字⇒ひらかな】ボタンを押す
  3. ひらがな欄が「しんかんせん1」になる。※辞書に未登録な文字の前後に[ _ ]は入らない。
  4. 【ひらがな⇒ローマ字】ボタンを押す
  5. ローマ字(大文字)欄に「SHINKANSEN1」、ローマ字(小文字)欄に「shinkansen1」と出るハズ
    • ※登録した漢字と登録した漢字の間に_が入る。(仕様

使い方7:辞書の登録

  1. 辞書ファイルを用意
  2. 全文をコピー
  3. 【クリップボード⇒辞書差替】ボタンをクリック

使い方8:複数の辞書を登録

  1. 1つ目の辞書は使い方7の通りに【クリップボード⇒辞書差替】ボタンで取込む
  2. 2つ目以降は【クリップボード⇒辞書追加】ボタンで取込む

使い方9:辞書を消そう。※ここ重要。

  1. F12でDevTools画面を開く
  2. 画面上の「アプリケーション」タグを選択
  3. 画面左の「ストレージ」の中の「LocalStrage」をクリックする
  4. 画面中央の「キー」の一覧に「kanjiYomi」を選択
  5. 右クリックして「削除」を選択

使い方は以上です。

ひらがな欄にカタカナを入力してもローマ字に変換できる。(ハズ

変数名中の「ロック」や「ファイル」等の英単語にするものは変則的な使い方ですが・・・

  • 漢字:「ロック」
  • ひらかな:「lock」

の様にして【単語の読みを辞書に登録】して【ひらかな⇒ローマ字】すると良いでしょう。

ローマ字にしたくない変数名中の「の」は

  • 漢字:「の」
  • ひらかな:「」

とすると出来るけど、「のはら」が「はら」になったり予想外のことが起きるでしょう。

新XXXXは「shinXXXX」にしたい場合は

  • 漢字:「新」
  • ひらかな:「しん@」

と読みに@を付けると読み仮名変換時に[ _ ]が付かない様にしたから

ローマ字にするとshinxxxxになるはず。

xxx年はxxxNenにしたい場合

  • 漢字:「年」
  • ひらかな:「@ねん」

と読みの先頭に@を付けると読み仮名変換時に[ _ ]が付かない様にしたから

◇◆◇◆◇◆◇◆

【単語の読みを辞書に登録】や【クリップボード⇒辞書差替】を登録するとLocalStrageに取り合えず保存するので

適当に【辞書⇒クリップボード】して辞書をバックアップすれば安全だろう。

LocalStrageはJSON式データで保存してるけど、

クリップボードは「漢字1\t読み仮名1\r\n」「漢字2\t読み仮名2\r\n」「漢字3\t読み仮名3\r\n」・・・形式なので、

多分、EXCEL⇔クリッップボード⇔画面で利用もできる。(知らんけど

なんでこんなのを作ったのかと云えば

Excelに日本語名とローマ字の対応表を作りXLOOKUPで引けばできることですが、

「xx処理yy関連zz対応www2」の様な日本語名がいっぱいあると登録するのに疲れてくるので

「xx処理」「yy部」「zz対応」「www」を一度登録すれば、

「aa処理yy関連zz関連www2」なら「aa処理」だけ登録すればいいものが欲しかった。(ダケ

それにしても・・・

3人居れば皆ローマ字はバラバラになるのは・・・(実に恐ろしい

試しに

IME用辞書のTEXT形式のものを見つけたので

置換前:^([^\t]+)\t([^\t]+)\t([^\t]+)$

置換後:\2\t\1

で正規表現ONで置換させて、うまく取り込める様だ。

しかし、LocalStrageの制約があるので、5MBを越える様な漢字の読みは登録できない。

ps.TEXTAREAタグに変えた

変数名の列を範囲指定して貼ってみると楽しいかもしれない。

  1. とりあえず、単漢読み仮名だけでも自動的にしたいなら
  2. 文化庁の「常用漢字一覧表」のPDFをダウンロード
  3. MS-WORDに強引に読ませる
  4. 全文をクリップボードに入れる
  5. テキストエディタに貼り付ける
  6. 雰囲気を見ながら
  7. 「^[\r\n]+」で、空行を削除 ※114行減る
  8. 「^\s(\S)\(\s+\)\s+」「\1\t」で、103行調整。
  9. 「^\s.*\r\n」で、タブや空白等で始まる行を削除 ※2249行減る
  10. 「^(\S+)\s+(\S+)\s+(.*)$」「\1\t\2」で、漢字と読みだけ残す ※1958行更新
  11. 「\(\S\)」で、(惡)などの旧字を削除 ※251行更新
  12. 「^漢字\t音訓\r\n」で、表タイトルを削除 ※115行削除
  13. 「^←→[^\r]*\r\n」で、削除 ※8行削除
  14. 微調整が必要なのが以下の14行。
    • 梅雨(つゆ),部屋(へや),今年(ことし), 昨日(きのう), お母(かあ)さん
    • 勺 シャク尺 シャク, 升 ショウ少 ショウ, 昭 ショウ宵 ショウ, 畝 せ瀬 せ, 浦 ホ補 ホ, 匁 もんめ夜 ヤ
    • 「人数」は, 陳 チン 陳列
    • 弁(/辨/瓣/辯/) ベン
  15. 全文コピって【クリップボード⇒辞書】ボタンを押す

と、してみたら良いかもしれない。

常用漢字⇒ジョウヨウカン_ジ⇒jou_you_kan_ji な具合

多分うまくできたと思うので、単漢字変換表サンプルのリンクをサンプルの画面に貼っておく。



[JavaScript] new ActiveXObject (progId)っぽく

IEのJavaScriptで

var cn = new ActiveXObject("ADODB.Connect");

みたいなことが出来たので、

Chromeでもできないのか考えてみた。

ActiveXObjectクラスのnew演算子でパラメータをplogIdとするOCXっぽいオブジェクトが戻って

くれれば嬉しいので、ストレートに

returnを使ってみた。

class ActiveXObject {
    constructor(className, json)
    {
        this.domain       = "xxx.xxx.xxx"; // この辺はC#側の設定を転記
        this.port         = "xxxx";         // この辺はC#側の設定を転記
        this.className    = className;
        this.objectID     = null;
        // 派生クラスの場合は再帰してしまうので、処理を省略
        if(typeof(className) !== 'undefined')
        {
            // classNameから当該クラスのJavaScript側クラスのオブジェクトを作成
            let cf = new Function('className', `return new ${className.replace('.', '_')}()`);
            let obj = undefined;
            try {
                obj = cf(className);
            } catch (ex) {
                throw new Error(`★new ActiveXObject("${className}")未定義クラス`);
            }
            // 初期設定値(json)があれば、設定する
            if (typeof (json) !== 'undefined') {
                obj['objectID'] = json['objectID'];
                obj.__setup__(json);
            }
            // 無ければ, C#のサーバに問い合わせる。
            else
            {
                // サーバー側にnewしたことを通知
                obj.init();
                // 放置してもサーバ側のオブジェクトは解放されないので、オブジェクトが不要になったら、適宜termメソッドを呼び出す事
                // this.term();
            }
            return obj; // new の戻り値
        }
    }
    // 設定する
    __setup__(json) {
        // プロパティにコピー
        for (let key of this.propertyNames) {
            let jj = json[key]
            if (typeof(jj) !== "undefined") {
                this[`_${key}`] = json[key];
            }
        }
        // プロパティ(コレクション)にコピー
        for (let key in this.propertyCollections) {
            let jj = json[key]
            if (typeof(jj) !== "undefined") {
                if (typeof (this[`_${key}`]) === "undefined" || this[`_${key}`] === null) {
                    this[`_${key}`] = {};
                }
                for (let idx in jj) {
                    let k = jj[idx].Name;
                    this[`_${key}`][`${k}`] = jj[idx];
                }
                // this[key]['Item'] = this[key]
            }
        }
    }
    // サーバー側にnewしたことを通知
    async init() {
        let json = await ActiveXObject.staticSendMessage('POST', this.domain, this.port, `ActiveXObject/${this.className}`);
        ActiveXObject.staticInit(this, json);
    }
    // オブジェクトの解放
    async term() {
        return await ActiveXObject.staticSendMessage('DELETE', this.domain, this.port, `ActiveXObject/${this.objectID}`);
    }
    // サーバー側にnewしたことを通知
    static staticInit(obj, json)
    {
        // 必須
        if (typeof (json["objectID"]) === 'Nothing')
        {
            alert("objectIDが取得できません。");
        }
        obj.objectID = json["objectID"];
        if (typeof (obj.onobjectID) !== 'undefined')
        {
            obj.onobjectID();
        }
        obj.SetProperties(json['activeXobject']);
        //
    }
    // 送信
    static staticSendMessage(method, domain, port, page, data)
    {
        let promise = new Promise( (resolve, reject) => {
            try {
                let parserFunction = this.parserFunction;
                let xhr = new XMLHttpRequest();
          // httpsにした方がいいのかもしれない
                let url = encodeURI(`http://${domain}:${port}/${page}`);
                xhr.open(method, url, true);
                xhr.responseType = 'text';
                xhr.send(this.staticEncodeHTMLForm(data));
                xhr.onload = function(e) {
                    if (xhr.readyState == 4) {
                        if (xhr.status == 200 ) {
                            try {
                                let json = xhr.response;
                                json = JSON.parse(json, parserFunction);
                                resolve(json);
                                return;
                            } catch (ex) {
                                console.log(`'${ex.toString()}'\n url:http://${domain}:${port}/${url}\n json:'${xhr.response}'`);
                            }
                        }
                    }
                };
            }
            catch (ex) {
                reject(ex);
                return;
            }
        });
        return promise;
    }
    // データをBODYの形式に変換
    static staticEncodeHTMLForm(data) {
        if (typeof(data) === 'undefined' || data === null)
        {
            return null;
        }
        let a = [];
        for (let aa of data) {
            a.push(aa);
        }
        return "params=" + JSON.stringify(a);
    }
    //
    static staticMakeUrlParameters(arg) {
        let rc = [];
        for (let idx = 1; idx <= arg.length; idx++) {
            rc.add(`p${idx}=JSON.stringify(${arg[idx - 1]})`);
        }
        return rc.join('&');
    }
    //
    static staticParserFunction(k,v)
    {
        try
        {
            if (typeof(v) === "string" && ((v.indexOf("function") === 0) || (v.indexOf("async") === 0)))
            {
                return eval(`(${v})`);
            }
            else
            {
                return v;
            }
        } catch (ex) {
            console.log(ex.toString());
        }
    }
}

newで使用する各ActiveXObjectのテンプレ・クラスは・・・

//  ADODB_Connection クラス
class ADODB_Connection extends ActiveXObject {
    constructor() {
        // とりあえず定番
        super();
        this.className = this.constructor.name.replace('_', '.');
        // プロパティ
        // 名前リストからプロパティを作成
        this.propertyNames = ['Attributes', 'CommandTimeout', 'ConnectionString', 'ConnectionTimeout', 'CursorLocation', 'DefaultDatabase', 'IsolationLevel', 'Mode', 'Provider', 'State', 'Version'];
        this.makeProperties(this.propertyNames);
        // プロパティ(コレクション)
        // 名前リストからダミーのメソッド(コレクション)を作成
        this.propertyCollections = {
            // https://docs.microsoft.com/ja-jp/office/client-developer/access/desktop-database-reference/errors-collection-properties-methods-and-events
            'Errors': {
                'properties': ['Count', 'Item'],
                'methods': ['Clear', 'Refresh'],
                'events': []
            },
            // https://docs.microsoft.com/ja-jp/office/client-developer/access/desktop-database-reference/properties-collection-properties-methods-and-events
            'Properties': {
                'properties': ['Count', 'Item'],
                'methods': ['Refresh'],
                'events': []
            }
        };
        this.makePropertiesCollection(this.propertyCollections);
        // メソッド
        // 名前リストからダミーのメソッドを作成
        this.methodNames = ['BeginTrans', 'CommitTrans', 'RollbackTrans', 'Cancel', 'Close', 'Execute', 'Open', 'OpenSchema'];
        this.makeMethods(this.methodNames);
        // イベント
        // 名前リストからダミーのメソッドを作成
        this.eventNames = ['BeginTransComplete', 'CommitTransComplete', 'RollbackTransComplete', 'ConnectComplete', 'Disconnect', 'ExecuteComplete', 'InfoMessage', 'WillConnect', 'WillExecute'];
        this.makeEvents(this.eventNames);
    }
}

“ADODB.Connection”というクラス名に使えなかいので、”ADODB_Connection”にしている。

makeXXXXは使いまわすのでActiveXObjectクラスで実装

    //
    /**
     * 名前リストからプロパティを作成
     * 
     * @param {String[]} propertyNames   名前リスト
     */
    makeProperties(propertyNames) {
        for (let propName of propertyNames) {
            Object.defineProperty(this, propName, {
                get() {
                    return this[`_${propName}`];
                },
                set(value) {
                    this[`_${propName}`] = value;
                }
            });
        }
    }
    /**
     * 名前リストからメソッドを作成
     * 
     * @param {String[]} methodNames   名前リスト
     */
    makeMethods(methodNames) {
        for (let methodName of methodNames) {
            this[methodName] = function () {
                let args = arguments;
                if (this.objectID == null) {
                    return new Promise(async (resolve, reject) => {
                        this.onobjectID = async function () {
                            let rc = await this[methodName].apply(this, args);
                            resolve(rc);
                            return;
                        }
                    });
                } else {
                    let path = `ActiveXObject/${this.objectID}/${methodName}`;
                    return new Promise(async (resolve, reject) => {
                        let json = await ActiveXObject.staticSendMessage('PUT', this.domain, this.port, path, args);
                        // エラーがあれば返す。
                        if (typeof (json['error']) !== 'undefined') {
                            reject(json['error']);
                            return;
                        }
                        //
                        this.SetProperties(json['activeXObject']);
                        //
                        let rc = json['returnValue'];
                        if (rc != null && rc["__constructor_name__"]) {
                            let className = rc["__constructor_name__"];
                            delete rc["__constructor_name__"];
                            let obj = new ActiveXObject(className, rc);
                            resolve(obj);
                            return;
                        }
                        resolve(rc);
                        return;
                    });
                }
            };
        }
    }
    /**
     * 名前リストからプロパティ(コレクション)を作成
     * 
     * @param {String[]} propertyCollectionNames   名前リスト
     */
    makePropertiesCollection(propertyCollections) {
        for (let collectionName in propertyCollections) {
            if (typeof (this[collectionName]) === "undefined") {
                this[collectionName] = {};
            }
            let co = this[collectionName];
            // コレクション本体
            Object.defineProperty(this, collectionName, {
                get() {
                    return this[`_${collectionName}`];
                },
                set(value) {
                    this[`_${collectionName}`] = value;
                }
            });
            // コレクションの設定内容
            let collection = propertyCollections[collectionName];
            // コレクションのプロパティ
            // property
            for (let propertyName of collection.properties) {
                Object.defineProperty(co, propertyName, {
                    get() {
                        return co[`_${propertyName}`];
                    },
                    set(value) {
                        co[`_${propertyName}`] = value;
                    }
                });
            }
            // コレクションのメソッド
            for (let methodName of collection.methods) {
                if (typeof (co[methodName]) === "undefined")
                {
                    co[methodName] = function () {
                        let args = arguments;
                        if (this.objectID == null) {
                            this.onobjectID = async function () {
                                let rc = await co[methodName].apply(co, args);
                                resolve(rc);
                                return;
                            }
                        } else {
                            let path = `ActiveXObject/${this.objectID}/${collectionName}/${methodName}`;
                            return new Promise(async (resolve, reject) => {
                                let json = await ActiveXObject.staticSendMessage('PUT', this.domain, this.port, path, args);
                                // エラーがあれば返す。
                                if (typeof (json['error']) !== 'undefined') {
                                    reject(json['error']);
                                    return;
                                }
                                //
                                this.SetProperties(json['activeXObject']);
                                //
                                let rc = json['returnValue'];
                                if (rc != null && rc["__constructor_name__"]) {
                                    let className = rc["__constructor_name__"];
                                    delete rc["__constructor_name__"];
                                    let obj = new ActiveXObject(className, rc);
                                    resolve(obj);
                                    return;
                                }
                                resolve(rc);
                                return;
                            });
                        }
                    };
                }
            }
            // event 未定
            //
        }
    }

コレクションはプロパティとメソッドのソースをマージした感じで雑になっている。

これで準備が整ったところで、

new ActiveXObject("ADODB.Connection");

すれば、いいはず。

これって、new 演算子をオーバーライドした事になるのかな?(知らんけど

真っ先にプロパティ・チェックするケースは見当たらなかったので、

最初にメソッド呼び出し時に、C#側からobjectIDが送られるまで待って処理する様にしている。

プロパティ参照時もobjectIDが送られるまで待って処理を入れた方がいいけど、

var table_name = schema.Fields["TABLE_NAME"].Value;

の様に呼び出されると、await が入れにくい。

うまくawaitが入っても、絶望的に読みにくくなりそう。

var table_name = await (await (await schema.Fields)["TABLE_NAME"]).Value;



【javascript】indexedDB

まだ、イマイチ感がある。

注意点

1.PC上のHTMLの場合、データベースのスコープ(有効範囲)はPC単位。

つまりPC上のHTMLで共有してしまう

2.データベースをオープンしたら、必ず自分でクローズすること

クローズし忘れると、次のオープン時にロックしやすい

大抵はF5のオートコミットで済むが、ブラウザを閉じないとダメな場合もある

successイベントは1回のみなので、try catchの後のfinallyでクロースすればいい

const request = window.indexedDB.open("TestDatabase");
request.addEventListener('success', (event) => {
  const database = event.target.result;
  try {
    ・・・データ処理・・・
  } catch (ex) {
    console.log(`${ex}`);
  } finally {
    database.close();
  }
});

しかし、Promiseを使って処理の同期を取りたい場合は・・・

const promise = new Promise( (resolve, reject) => {
  const request = window.indexedDB.open("TestDatabase");
  request.addEventListener('success', (event) => {
    const database = event.target.result;
    try {
      ・・・データ処理・・・
   resolve(`xxxx(...): success`);
    } catch (ex) {
      console.log(`${ex}`);
   reject(`xxxx(...): catch(${ex})`);
    } finally {
      database.close();
    }
  });
  *** エラー処理とか ***
});
return promise;

とすると、resolveの後にfinally句のクローズ処理が通るかどうか?

あまり自信が無い。

と云うのも

function test() {
  return  true;
  alert('OK');
}

のalert(‘OK’);がいつのまにか処理されなくなってたからだ。

upgradeneeded発生時は、後にsuccessイベントが続くので、successイベントに任せればいい

errorイベントも忘れずにクローズ。

request.addEventListener('error', (event) => {
  const error = event.target.error;
  database.close();
  reject(`xxxx(...): error\n${error}`);
});

3.オブジェクトストアの生成・削除はデータベースのバージョンアップ時のみ

4.インデックスの生成・削除もデータベースのバージョンアップ時のみ

5.だが、トランザクションはデータベースのバージョンアップ時は不可

6.トランザクションも自分でcommitabortすること

commitabortを忘れるとタイムアウトするまで次の処理がロックする

const trans = database.transaction(オブジェクトストア・リスト);
try {
  const objectStore = trans.objectStore(オブジェクトストア);
  ***データ処理***
  trans.commit();
} catch (ex) {
  trans.abort();
  throw ex;
}

だからと云ってcursorのsuccessイベントは何回も降ってくることが多いので

毎回commitすると、2周目でつまずく

const trans = database.transaction(オブジェクトストア・リスト);
try {
  const objectStore = trans.objectStore(オブジェクトストア);
  const cursor objectStore.createCursor().onsuccess(event => {
    const cursor = event.target.result;
    ***データ処理***
    cursor.continue();
    trans.commit(); //毎回コミットは・・・
  });
} catch (ex) {
  trans.abort();
  throw ex;
}

7.インデックスの生成・削除できるタイミングはオブジェクトストアの生成直後だけ

const request = indeedDB.openDatabase(データベース名)
request.addEventListener(`onupgradeneed', (event) => {
  const database = event.target.result;
  const objectStore = database.createObjectStore(オブジェクトストア名);
  objectStore.createIndex(インデックス名);
  objectStore.deleteIndex(インデックス名);
});

後でインデックスを調整したい場合は、オブジェクトストアのexports機能を自作して、イジって、importsするしかない。

8.カーソルの生成はデータベースのバージョンアップ時は不可だと思う

9.カーソルのsuccessイベントはカーソルの移動回数分繰り返すので、Promise使った方が良さそう

最終のsuccessイベントは、event.target.result === undefined なので、うっかりcursor.valueすると痛い。resolveだけ処理すればいい。

const trans = database.transaction(オブジェクトストア名);
const promise = new Promise( (resolve, reject) => {
  try {
    const objectStore = trans.objectStore(オブジェクトストア名);
    const cursor = objectStore.openCursor();
    cursor.onsuccess = (event) => {
      var cursor = event.target.result;
      if (cursor) {
        console.log(`${cursor.key} is ${cursor.value}.`);
        cursor.continue();
      } else {
        console.log("end");
        resolve(true);
      }
    };
    cursor.onerror = (event) => {
      const error = event.target.error;
      reject(error);
    };
  } catch (ex) {
    reject(ex);
  }
});
return promise;

な感じだった。

データベースの構成をイジる処理をコードしてみると、

何かと面倒くさかったので、

全データベースまたはオブジェクトストア単位でのexportsimports機能を作って、

データを編集するコード(createDatabasedeleteDatabaseとか)を

使いまわしをした方が良さそう。

どうせやることは、

class IndexedDbOp {
  constractor(json) {
    this.json;
  };
  const createDatabase = (databaseName) => {
    this.json.databases[this.json.databases.length] = {'database':{
    'name': databaseName,
    'version': 1
    }}
    return this;
  }
  const deleteDatabase = (databaseName) => {
    this.json.databases = this.json.databases.filter( database => database.name !== databaseName );
    return this;
  }
  ・・・以下同文・・・
}

なのにブラウザにはindexedDBのexportsimports機能が付いていない不思議。

※上記のソースコードは雰囲気です。



[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