quicker-worker: 优雅的快速使用web worker进行开发

上一篇介绍了vue-worker,受其启发,打算自己写两个函数,用来快速实现自己的worker需求。vue-worker作者说无法做到关闭worker,我要弥补这个问题。废话不多,我已经把代码托管到github,点击这里看源码。

没错,两个个函数就是你要的一切!其中,真正对外的接口就是create和run两个函数。create是创建一个可以后续操作的worker实例,而run就是只执行一遍就自己关掉的一次性worker操作。

API

create(factory, options)

create函数通过传入一个要在worker线程中执行的函数,返回一个worker实例,这个worker实例就是通过new Worker返回的实例。同时,它还多出两个接口:.invoke和.close,用于便捷操作。其实这两个接口可以使用原生的postMessage和terminate方法替换,不过因为我们使用到了blob,所以还要销毁我们创建的blob,所以在close方法的时候要执行这一步。

下面来看下用法:

let wk = create(arr => {
  arr.forEach(item => item.name = item.id + item.time)
  // 如果arr有100k行
  return arr // 这一行必须要
})
// 这个时候我们只是创建了一个用来处理一个100k行的worker方法,接下来我们来调用它
... // 你的其他js代码
wk.invoke([...]) //传入一个100k行的数组进行运算
.then(output => {
  console.log(output)
  wk.close() // 关掉这个worker释放线程
}) // output就是我们要的结果

factory函数的一些要求和使用中的一些注意点

  • 是在worker线程中执行,所以不能进行DOM等操作,只能用于一些计算性操作
  • 如果要得到返回结果,必须return一个返回值
  • 参数个数问题:如果只有一个参数,那么wk.invoke()也发送过去一个参数,这是等价的,如果factory函数有多个参数,那么wk.invoke()发送过去一个数组,这个数组中的元素就是参数列表,这个数组会被用作apply的第二个参数,所以这个中情况下invoke的数据和factory的第一个参数不等价,这点要极其注意!

有关options和更多信息,可以在我那个github上阅读Readme,不过是英文版的,英文版的介绍比这里更加详细,如果你阅读没有障碍的话,可以直接阅读英文。

wk.invoke([...args])

上面通过create返回了一个wk,它是一个真实的worker实例,但是我们给它附加了两个新方法,一个是invoke。invoke基于worker.postMessage,不过进行了封装,在post内部实行了wk的onmessage和onerrer监听,并把整个post结果返回一个promise,所以post之后要用then来得到运算的结果。

wk是可以复用的,通过create创建的实例仅仅是在worker线程内存中驻用了一个函数,这个函数本身并不运行,需要调用wk.invoke来执行这个函数。所以,wk.invoke可以反复调用,针对你不同的要用来运算的数据,传入后得到对应的结果。

wk.close()

close方法除了关闭worker线程之外,还清除了我们在blob中创建的内容,同时把wk置空,实现清除效果。

run(factory, args)

create是创建了一个worker实例对象,可以反复调用invoke接口来执行内部的那个函数。但有的时候我们只需要在worker中运算一次,那么这种情况下就不需要执行上面那么多步骤。于是我封装了run函数,其实是把create函数的方法全部调用一遍。

run(arr => {
  arr.forEach(item => item.name = item.id + item.time)
  // 如果arr有100k行
  return arr // 这一行必须要
}, [...100k rows...])
.then(output => {
  // ...
})

通过这个函数,你就可以一次性执行,执行完之后,就会被销毁。

Blob

源码中有一个makecontents函数,这个不是对外的接口,而是内部用于创建blob contents的函数。首先要解释一下blob以及它在这里的作用。

blob是一个file-like对象,保持raw data,可以在这里了解具体的解释。它有什么用呢?可以用来在本地加载图片、文件的原始数据,而且可以提供一个blob:开头的url,这样,显示本地图片就无需上传到服务器上使用一个服务器端的url来显示了。同样的道理,js也是一样的,通过一个本地的blob url来代替服务端url,把js源码放到这个blob中,这样就可以实现无需一个真实的js文件,而是把js的原始脚本放在内存中,还可以执行。

我们来看下blob的基本用法:

var blob = new Blob([contents], {type: 'application/javascript'})
var url = URL.createObjectURL(blob) // URL是一个window下的全局变量

Blob对象第一个参数是一个数组,数组就一个元素,这个元素的内容就是raw data,可以是图片的raw data也可以是js脚本的内容(字符串)。这也是为什么makecontents函数返回的是一个字符串。

通过URL.createObjectURL方法就可以创建一个类似

blob:d3958f5c-0777-0845-9dcf-2cb28783acaf

一样的url,直接访问这个地址还真的可以看到对应的文件内容。

这就是我们需要的最核心的关于blob的内容。

web worker

关于web worker的具体内容我就不多解释了,这里主要讲一下在我们这里实现的逻辑。

我们借助makecontents函数,在blob的帮助下,相当于创建了一个.js文件,这个文件的内容将会在worker线程中执行。

注意makecontents函数返回的是一个字符串,其中有一句var fun = ${factory},字符串在解析的时候,factory函数体会被自动转换为字符串,所以实际上最后你会发现,在blob中,这段字符串会按照我们想要的方式呈现出来。

在worker线程中,我们使用self代替global变量,但是还要注意一点,因为makecontents函数中的字符串,是不会被babel转码的,字符串怎么可能被解析呢?所以,在字符串中,一定要使用浏览器可以执行的语法,浏览器不支持的语法还是不要用。

2017-07-25 | ,