web workers简介(二)动态建立worker

基础使用javascript

动态内联workerhtml

subworkerjava


web workers简介(二)动态建立worker

你们好,今天在这里简单介绍一下如何动态的建立内联的web workers。git

动态建立worker

在介绍worker-loader时,咱们已经了解到能够经过BlobcreateObjectURL来建立内联的web worker,这使得web worker的使用变得更加灵活。github

接下来,让咱们一块儿来对此进行尝试。新建workify.js,而且编写咱们的页面代码:web

// index.html
<script type="text/javascript" src="./workify.js"></script>
<script type="text/javascript">
function add(a, b) {
  return a + b;
}
async function initFunc() {
  const workerAdd = workify(add);
  console.log('workerAdd', await workerAdd(23, 16));
}
initFunc();
</script>
复制代码

咱们但愿经过workify方法建立一个内联的web worker的代理,而且能够用async/await的形式来调用这个方法。数组

接着咱们将编写咱们的workify方法。首先添加一些工具方法。bash

// workify.js
(function (g) {
  const toString = function toString(t) {
    return Function.prototype.toString.call(t);
  };
  const getId = function getId() {
    return (+new Date()).toString(32);
  };
  
  ...
  
  const workify = function workify(target) {
    ...
  }
  
  g.workify = workify;
})(window);
复制代码

接下来,咱们将建立内联代码的web worker:app

const code = `(${toString(proxy)})(${toString(target)})`;
const blob = new Blob([code]);
const url = URL.createObjectURL(blob);
const worker = new Worker(url);
复制代码

这里咱们拼接的代码,将会把目标函数做为参数传给咱们的proxy函数,proxy函数会负责处理web worker调用目标函数并与主线程进行通讯(经过调用postMessage和设置onmessage)。async

接下来,在workify中咱们将设置workeronmessage方法。同时咱们将为worker添加一个send方法,这个方法会使用postMessage发出消息,并返回一个Promise

最后workify会返回一个方法,这个方法会经过worker.send发送消息并返回它的Promise

worker.onmessage = function (ev) {
  //
};
worker.send = function ({ type, data }) {
  //
}
const rtn = function rtn(...args) {
  return worker.send({
    type: 'exec',
    data: args,
  });
};
return rtn;
复制代码

由于咱们须要在worker完成任务时知道须要去resolve哪一个Promise,所以咱们将在postMessage中发送一个id,并由worker再返回来:

worker._cbs = {};
worker.onmessage = function (ev) {
  const { type, id, data } = ev.data;
  if (type === 'exec') {
    worker._cbs[id](data);
  }
};
worker.send = function ({ type, data }) {
  return new Promise((res) => {
    const id = getId();
    worker._cbs[id] = (data) => {
      res(data);
    };
    worker.postMessage({
      id,
      type,
      data,
    });
  });
}
复制代码

以后咱们再来实现proxy方法,既web worker端的逻辑:

const proxy = function proxy(target) {
  self.onmessage = function (ev) {
    const { type, data, id } = ev.data;
    let rtn = null;
    if (type === 'exec') {
      rtn = target.apply(null, data);
    }
    self.postMessage({
      id,
      type,
      data: rtn,
    });
  };
};
复制代码

咱们使用接收到的参数来调用目标函数,并将结果和id发送回去。

若是须要经过importScripts加载代码,咱们能够在目标函数中直接使用importScripts,或将须要加载的代码数组做为另外一个参数传入proxy:

const proxy = function proxy(target, scripts) {
  if (scripts && scripts.length) importScripts.apply(self, scripts);
  ...
}
复制代码

如上,咱们已经能够将函数内联为web worker。接下来,我还但愿能将Class一样内联为web worker。

class Adder {
  constructor(initial) {
    this.count = initial;
  }
  add(a) {
    this.count += a;
    return this.count;
  }
}
async function initClass() {
  let WAdder = workify(Adder);
  let instance = await new WAdder(5);
  console.log('apply add', await instance.add(7));
  console.log('get count', await instance.count);
}
initClass();
复制代码

首先,咱们改变rtn的代码,以判断其是否经过new调用:

const rtn = function rtn(...args) {
  if (this instanceof rtn) {
    return worker.send({
      type: 'create',
      data: args,
    });
  } else {
    return worker.send({
      type: 'exec',
      data: args,
    });
  }
};
复制代码

接下来咱们修改work.onmessage,根据事件类型作出不一样处理(在此处不一样的仅create事件):

worker.onmessage = function (ev) {
  const { type, id, data } = ev.data;
  if (type === 'create') {
    worker._cbs[id](_proxy(worker));
  } else {
    worker._cbs[id](data);
  }
};
复制代码

咱们将先支持如下4类事件:

  • exec:调用函数
  • create:建立实例
  • apply:调用实例方法
  • get:获取实例属性

相应的proxy函数中定义的onmessage也要修改:

self.onmessage = function (ev) {
  const { type, data, id } = ev.data;
  let rtn = null;
  if (type === 'exec') {
    rtn = target.apply(null, data);
  } else if (type === 'create') {
    instance = new target(...data);
  } else if (type === 'get') {
    rtn = instance;
    for (let p of data) {
      rtn = rtn[p];
    }
  } else if (type === 'apply') {
    rtn = instance;
    for (let p of data.path) {
      rtn = rtn[p];
    }
    rtn = rtn.apply(instance, data.data);
  }
  
  ...

};
复制代码

对应的逻辑分别为生成示例、获取属性与调用方法。

worker.onmessage中,咱们经过_proxy(worker)来返回一个代理,这是比较tricky的一段代码。

咱们但愿咱们返回的代理对象,能够得到任意获取属性、任意调用代码,并将调用web worker相应的行为。

所以这里咱们使用了Proxy,而且其目标是一个函数,这样咱们就能代理期get(获取属性)和apply(调用)两种行为。在get中,咱们经过递归的使用_proxy来实现深度代理。咱们经过path来记录当前路径,当获取的属性为then时,例如await instance.countpath['count'],咱们将使用worker.send来获取相应的属性并返回其then;而若当前path为空,咱们能够直接返回null,表示当前对象非thenable并中断Promise链。

const _proxy = function _proxy(worker, path) {
  path = path || [];
  return new Proxy(function(){}, {
    get: (_, prop, receiver) => {
      if (prop === 'then') {
        if (path.length === 0) return null;
        const p = worker.send({
          type: 'get',
          data: path,
        });
        return p.then.bind(p);
      }
      return _proxy(worker, path.concat(prop));
    },
    apply: (_0, _1, args) => {
      return worker.send({
        type: 'apply',
        data: {
          path,
          data: args,
        },
      });
    },
  });
};
复制代码

小结

今天介绍了如何经过Blob来建立内联的web workers。接下来将要介绍一下如何实现与subworker类似的功能。

代码

(function (g) {
  const toString = function toString(t) {
    return Function.prototype.toString.call(t);
  };
  const getId = function getId() {
    return (+new Date()).toString(32);
  };
  const proxy = function proxy(target, scripts) {
    if (scripts && scripts.length) importScripts.apply(self, scripts);
    let instance;
    self.onmessage = function (ev) {
      const { type, data, id } = ev.data;
      let rtn = null;
      if (type === 'exec') {
        rtn = target.apply(null, data);
      } else if (type === 'create') {
        instance = new target(...data);
      } else if (type === 'get') {
        rtn = instance;
        for (let p of data) {
          rtn = rtn[p];
        }
      } else if (type === 'apply') {
        rtn = instance;
        for (let p of data.path) {
          rtn = rtn[p];
        }
        rtn = rtn.apply(instance, data.data);
      }
      self.postMessage({
        id,
        type,
        data: rtn,
      });
    };
  };

  const _proxy = function _proxy(worker, path) {
    path = path || [];
    return new Proxy(function(){}, {
      get: (_, prop, receiver) => {
        if (prop === 'then') {
          if (path.length === 0) return null;
          const p = worker.send({
            type: 'get',
            data: path,
          });
          return p.then.bind(p);
        }
        return _proxy(worker, path.concat(prop));
      },
      apply: (_0, _1, args) => {
        return worker.send({
          type: 'apply',
          data: {
            path,
            data: args,
          },
        });
      },
    });
  };

  const workify = function workify(target, scripts) {
    const code = `(${toString(proxy)})(${toString(target)}, ${JSON.stringify(scripts)})`;
    const blob = new Blob([code]);
    const url = URL.createObjectURL(blob);
    const worker = new Worker(url);
    worker._cbs = {};
    worker.onmessage = function (ev) {
      const { type, id, data } = ev.data;
      if (type === 'exec') {
        worker._cbs[id](data);
      } else if (type === 'create') {
        worker._cbs[id](_proxy(worker));
      } else if (type === 'apply') {
        worker._cbs[id](data);
      } else if (type === 'get') {
        worker._cbs[id](data);
      }
    };
    worker.send = function ({ type, data }) {
      return new Promise((res) => {
        const id = getId();
        worker._cbs[id] = (data) => {
          res(data);
        };
        worker.postMessage({
          id,
          type,
          data,
        });
      });
    }
    const rtn = function rtn(...args) {
      if (this instanceof rtn) {
        return worker.send({
          type: 'create',
          data: args,
        });
      } else {
        return worker.send({
          type: 'exec',
          data: args,
        });
      }
    };
    return rtn;
  };
  g.workify = workify;
})(window);
复制代码

参考

pshihn/workly

相关文章
相关标签/搜索