MDN wants to talk to developers like you: https://qsurvey.mozilla.com/s3/a3e7b5301fea

缓存已编译的WebAssembly模块

这篇翻译不完整。请帮忙从英语翻译这篇文章

对于提高引用的性能来说,缓存是很有用的——我们可以在客户端存储已编译的WebAssembly模块,从而可以避免每次都下载和编译它们。本文解释了这方面的最佳实践。

使用IndexDB实现缓存

IndexedDB是一个事务型数据库系统,它允许你在客户端存储和获取结构化数据。它适合在本地保存包括了应用程序的状态的资源,包括文本、二进制大对象以及其他任何可以克隆的对象。

这包括已编译的wasm模块(WebAssembly.Module JavaScript对象)。

建立缓存库

因为IndexedDB在一定程度上是老式风格的API,所以,我们想提供一个库函数以便加快写缓存代码的速度并使它能够更好的与当今更现代的API配合。

在我们的wasm-utils.js脚本库中,你会发现instantiateCachedURL()——该函数使用给定版本的dbVersion获取给定url的wasm模块,使用给定的importObject实例化它并且返回一个将会解析成最终的wasm实例的promise。而且,它会创建一个用来把已编译的wasm模块缓存起来的数据库,尝试在数据库中存储新的模块并且从数据库中获取之前缓存的模块从而使你免于再次下载它们。

: 整个网站的wasm缓存(不只是给定的URL)是通过传入到函数中的dbVersion进行版本控制的。如果wasm模块代码更新了或者它的URL发生了变化,你需要更新dbVersion。对于instantiateCachedURL()的任何后续调用将会清除掉全部的缓存从而使你避免使用过时的模块。

该函数从定义一些必要的常量开始:

function instantiateCachedURL(dbVersion, url, importObject) {
  const dbName = 'wasm-cache';
  const storeName = 'wasm-cache';

建立数据库

在instantiateCachedURL()中的第一个辅助函数openDatabase()创建一个存储wasm模块的对象存储空间以及在dbVersion更新后清除数据库;它返回一个解析成新的数据库的promise。

  function openDatabase() {
    return new Promise((resolve, reject) => {
      var request = indexedDB.open(dbName, dbVersion);
      request.onerror = reject.bind(null, 'Error opening wasm cache database');
      request.onsuccess = () => { resolve(request.result) };
      request.onupgradeneeded = event => {
        var db = request.result;
        if (db.objectStoreNames.contains(storeName)) {
            console.log(`Clearing out version ${event.oldVersion} wasm cache`);
            db.deleteObjectStore(storeName);
        }
        console.log(`Creating version ${event.newVersion} wasm cache`);
        db.createObjectStore(storeName)
      };
    });
  }

Looking up modules in the database

Our next function — lookupInDatabase() — provides a simple promise-based operation for looking up the given url in the object store we created above. It resolves with the stored compiled module, or rejects with an error.

  function lookupInDatabase(db) {
    return new Promise((resolve, reject) => {
      var store = db.transaction([storeName]).objectStore(storeName);
      var request = store.get(url);
      request.onerror = reject.bind(null, `Error getting wasm module ${url}`);
      request.onsuccess = event => {
        if (request.result)
          resolve(request.result);
        else
          reject(`Module ${url} was not found in wasm cache`);
      }
    });
  }

Storing and instantiating modules

Next, we define a function storeInDatabase() that fires off an async operation to store a given wasm module in a given database.

  function storeInDatabase(db, module) {
    var store = db.transaction([storeName], 'readwrite').objectStore(storeName);
    var request = store.put(module, url);
    request.onerror = err => { console.log(`Failed to store in wasm cache: ${err}`) };
    request.onsuccess = err => { console.log(`Successfully stored ${url} in wasm cache`) };
  }

Last but not least, we define a final helper function — fetchAndInstantiate(), which fetches the given url, compiles it into a Module, and instantiates the Module with the given import object.

  function fetchAndInstantiate() {
    return fetch(url).then(response =>
      response.arrayBuffer()
    ).then(buffer =>
      WebAssembly.instantiate(buffer, importObject)
    )
  }

Using our helper functions

With all the Promise-based helper functions defined, we can now express the core logic of an IndexedDB cache lookup. We start by trying to open a database, then see if we already have a compiled Module with the key url stored in the given db:

  return openDatabase().then(db => {
    return lookupInDatabase(db).then(module => {

If we do, we instantiate it with the given import object:

      console.log(`Found ${url} in wasm cache`);
      return WebAssembly.instantiate(module, importObject);
    },

If not, we compile it from scratch and then store the compiled Module in the database with a key of url, for next time we want to use it:

    errMsg => {
      console.log(errMsg);
      return fetchAndInstantiate().then(results => {
        storeInDatabase(db, results.module);
        return results.instance;
      });
    })
  },

Note: It is for this kind of usage that WebAssembly.instantiate() returns both a Module and an Instance: the Module represents the compiled code and can be stored/retrieved in IDB or shared between Workers via postMessage(); the Instance is stateful and contains the callable JavaScript functions, therefore it cannot be stored/shared.

If opening the database failed (for example due to permissions or quota), we fall back to simply fetching and compiling the module and don't try to store the results (since there is no database to store them into).

  errMsg => {
    console.log(errMsg);
    return fetchAndInstantiate().then(results =>
      results.instance
    );
  });
}

Caching a wasm module

With the above library function defined, getting a wasm module instance and using its exported features (while handling caching in the background) is as simple as calling it with the following parameters:

  • A cache version, which — as we explained above — you need to update when any wasm module is updated or moved to a different URL.
  • The URL of the wasm module you want to instantiate.
  • An import object, if required.
const wasmCacheVersion = 1;

instantiateCachedURL(wasmCacheVersion, 'test.wasm').then(instance =>
  console.log("Instance says the answer is: " + instance.exports.answer())
).catch(err =>
  console.error("Failure to instantiate: " + err)
);

You can find the source code for this example on GitHub as indexeddb-cache.html (see it live also).

文档标签和贡献者

 此页面的贡献者: kingysu
 最后编辑者: kingysu,