1

辞書ファイル (1MB) を javascript 配列に (ajax 経由で) ロードする Web アプリケーションがあります。Mobile Safari が 10 秒後にクラッシュする理由がわかりました。しかし、今私が疑問に思っているのは、この問題をどのように回避するのですか?

上記のリンクでは、 を使用することを提案してsetIntervalいますが、これは辞書ファイルを断片に分割し、それらを 1 つずつロードする必要があることを意味します。これは確かに実行できますが、インターネットの速度を考慮して多くのチャンクを作成する必要があり、リクエストが多すぎるとページの読み込みに永遠に時間がかかります (チャンクを大きくしすぎると、一部のモバイル ユーザーが特定の 10 秒間にチャンクをダウンロードすることはできません)。

ですから、私の質問は次のとおりです。この種の問題に遭遇した人はいますか?どのように対処しましたか? 正しい方向への一般的なプッシュは高く評価されます。

編集:これは、辞書をロードするために使用するjsコードです:

var dict = new Trie();

$.ajax({
    url: 'data/dictionary_342k_uppercase.txt',
    async: true,
    success: function (data) {
        var words = data.split('\n');
        for (var i = words.length - 1; i >= 0; i--) {
            dict.insert(words[i]);
        }           
    },
    error: function(){
        $('#loading-message').text("Problem s rječnikom");
    }
});

Trie.js:

function Trie () {
  var ALPHABET_SIZE = 30;
  var ASCII_OFFSET = 'A'.charCodeAt();

  this.children = null;
  this.isEndOfWord = false;

  this.contains = function (str) {
    var curNode = this;

    for (var i = 0; i < str.length; i++) {
      var idx = str.charCodeAt(i) - ASCII_OFFSET;
      if (curNode.children && curNode.children[idx]) {
        curNode = curNode.children[idx];
      } else {
        return false;
      }
    }

    return curNode.isEndOfWord;
  }

  this.has = function (ch) {
    if (this.children) {
      return this.children[ch.charCodeAt() - ASCII_OFFSET] != undefined;
    }
    return false;
  }

  this.next = function (ch) {
    if (this.children) {
      return this.children[ch.charCodeAt() - ASCII_OFFSET];
    }
    return undefined;
  }

  this.insert = function (str) {
    var curNode = this;

    for (var i = 0; i < str.length; i++) {
      var idx = str.charCodeAt(i) - ASCII_OFFSET;

      if (curNode.children == null) {
        curNode.children = new Array(ALPHABET_SIZE);
        curNode = curNode.children[idx] = new Trie();
      } else if (curNode.children[idx]) {
        curNode = curNode.children[idx];
      } else {
        curNode = curNode.children[idx] = new Trie();
      }
    }

    curNode.isEndOfWord = true;
    return curNode;
  }
}
4

2 に答える 2

7

これは、JS で処理を開始すると、非常に一般的な問題です。Mobile Safari の問題が原因である場合は、ここで CPU 時間がどこにかかっているかを把握する必要があります。

私はそれdict.insert()が呼び出しではなくループであると想定していdata.split()ます (それは管理が少し難しいでしょう)。

ここでの考え方は、dict.insert()ループを機能ブロックに分割して、シーケンス ループ内で非同期に呼び出すことができるようにすることです (これが関数のsetupBuildActions機能です)。最初のブロックの後、後続の各ブロックが 経由setTimeoutで呼び出されます。これにより、JS ランタイムの関数時間カウンターが効果的にリセットされます (これがプロセスを強制終了しているようです)。

シーケンサー関数を使用すると、関数が実行される順序も制御できます (関数は常にここで生成された順序で実行され、2 つ以上の関数が同時に実行されるようにスケジュールされることはありません)。setTimeoutこれは、コールバックなしで何千もの呼び出しを開始するよりもはるかに効果的です。コードは実行順序の制御を維持し (これは、実行中に変更を加えることができることも意味します)、スケジュールされた実行要求によって JS ランタイムが過負荷になることはありません。

また、シーケンスの例についてはhttps://github.com/michiel/sequencer-jsのノード プロジェクトを確認してください。また、説明についてはhttp://ejohn.org/blog/how-javascript-timers-work/setTimeoutさまざまなプラットフォームで。

var dict = new Trie();

// These vars are accessible from all the other functions we're setting up and
// running here

var BLOCKSIZE     = 500;
var words         = [];
var buildActions  = [];

function Sequencer(funcs) {
  (function() {
    if (funcs.length !== 0) {
      funcs.shift()(arguments.callee);
    }
  })();
}

// Build an Array with functions that can be called async (using setTimeout)

function setupBuildActions() {
  for (var offset=0; offset<words.length; offset+= BLOCKSIZE) {
    buildActions.push((function(offset) {
      return function(callback) {
        for (var i=offset; i < offset + BLOCKSIZE ; i++) {
          if (words[i] !== null) { // ugly check for code brevity
            dict.insert(words[i]);
          }
        }           
        // This releases control before running the next dict.insert loop
        setTimeout(callback, 0);
      };
    })(offset));
  }
}

$.ajax({
    url: 'data/dictionary_342k_uppercase.txt',
    async: true,
    success: function (data) {
      // You might want to split and setup these calls 
      // in a setTimeout if the problem persists and you need to narrow it down
      words = data.split('\n');
      setupBuildActions();
      new Sequencer(buildActions);
    },
    error: function(){
      $('#loading-message').text("Problem s rječnikom");
    }
});
于 2013-04-22T22:11:30.543 に答える
0

setTimeout を使用して、実際の単語のトライへの挿入を遅らせる例を次に示します。元の文字列をバッチに分割し、setTimeout を使用して単語の各バッチを挿入する処理を延期します。この例のバッチ サイズは 5 ワードです。

実際のバッチ挿入は、ブラウザー内の後続のイベント ハンドラーとして発生します。

単語をバッチに分割するだけでは時間がかかりすぎる可能性があります。この問題が発生した場合は、setTimeout() 呼び出しを連鎖できることを覚えておいてください。たとえば、しばらく反復してから setTimeout を使用して別のイベントをスケジュールし、さらに反復してから setTimeout を再度実行するなどです。

    function addBatch(batch)
    {
            console.log("Processing batch:");
            for (var i = 0; i < batch.length; i++)
                    console.log(batch[i]);
            console.log("Return from processing batch");
    }

    var str = "alpha\nbravo\ncharlie\ndelta\necho\nfoxtrot\n" + 
              "golf\nhotel\nindia\njuliet\nkilo\nlima\n" + 
              "mike\nnovember\noscar\npapa\nquebec\n" + 
              "romeo\nsierra\ntango\nuniform\n" + 
              "victor\nwhiskey\nxray\nyankee\nzulu";

    var batch = []
    var wordend;
    for (var wordstart = 0; wordstart < str.length; wordstart = wordend+1)
    {
            wordend = str.indexOf("\n", wordstart);
            if (wordend < 0)
                    wordend = str.length;
            var word = str.substring(wordstart, wordend);
            batch.push(word);

            if (batch.length > 5)
            {
                    setTimeout(addBatch, 0, batch);
                    batch = [ ];
            }
    }
    setTimeout(addBatch, 0, batch);
    batch = [ ];
于 2013-04-22T01:31:03.580 に答える