Skip to content

Main Thread 可以使用 new Worker() 连接到指定 worker

而在 Worker Thread 中,顶级 self 对象指向 DedicatedWorkerGlobalScope,即专用 worker 全局作用域

3-1.Main Thread

js
// Main Thread
const worker = new Worker('./worker.js', {
  type: 'module'
})

worker.postMessage('Post Message from Main Thread')

worker.onmessage = (event) => {
  console.log('worker.onmessage', event)
}

worker.onmessageerror = (event) => {
  console.log('worker.onmessageerror', event)
}

worker.onerror = (event) => {
  console.log('worker.onerror', event)
}

1.创建worker

js
const worker = new Worker(url, options)
  • url: 表示可执行脚本文件地址(必须是 same-origin)或者 Base64 地址。

  • options

    • type: classic(默认)、 module(以 esmodule 解析脚本)。
    • credentials: omit (默认)、same-origininclude
    • name:自定义名字

TIP

url 可以是绝对路径或者相对路径,但也可以是 Base64 地址(MIME 类型是 application/javascript 等可解析形式)。

js
const worker = new Worker('./worker.js')
js
const customWorker = `
self.addEventListener('message', e => {
  console.log(e.data)
})
`

const worker = new Worker(`data:application/javascript,${encodeURIComponent(customrWorker)}`)

2.实例方法

  1. postMessage(message, transfer)

    • message:

    mesage 需要支持被结构化克隆算法处理,会被分发到 event.data

    • transfer

    一个可选的、会被转移所有权的可转移对象数组。

    如果一个对象的所有权被转移,它将在发送它的上下文中变为不可用(中止),而仅在接收方的 worker 中可用。

    ArrayBufferMessagePortImageBitmap 类的实例才是可转移对象,才能够被转移。不能将 null 作为 transfer 的值。

    See Transfer Example.

  2. terminate()

Main Thread 中断 worker

js
const worker = new Worker(url, options)

worker.terminate()

TIP

Main Thread 中断 worker 的话,那么 Main Thread 将不会再接收 worker 线程中的异步结果(无论是微任务还是宏任务)。

3.事件

  1. message

Main thread 接收来自 worker thread 中利用 postMessage 传递的信息。

  1. messageerror

worker 接收到一条无法被反序列化的消息时触发。

  1. error

worker 发生错误时触发。

  • SecurityError:脚本地址不合法或者不满足 same-origin 同源策略;
  • NetworkError:脚本的 MIME 类型不正确,可以是 text/javascript其他可解析类型
  • SyntaxError:脚本语法错误。

3-2.Worker Thread

js
// Worker Thread
self.addEventListener('message', (event) => {
  console.log('dedecated.onmessage', event)
})

self.postMessage('Post Message from Worker Thread')

此作用域下,selfthis 均指向 DedicatedWorkerGlobalScope

1.实例属性

  1. name

Main Thread 中利用 new Worker(url, options) 创建时,可使用 options.name 进行定义。

2.实例方法

  1. postMessage(message, transfer)

  2. close()

    Worker Thread 中断 worker

    TIP

    Worker Thread 中断 worker 的话,那么 Worker Thread 会中止宏任务队列,不会中止微任务队列。

3.事件

  1. message

  2. messageerror

3-3.中断与任务队列

在前文中,我们已经提到,在 Main Thread 中断 workerWorker Thread 中断 worker 表现并不一致。

主要逻辑为:

TIP

Main Thread 中断 worker 的话,那么 Main Thread 将不会再接收 worker 线程中的异步结果(无论是微任务还是宏任务)。

Worker Thread 中断 worker 的话,那么 Worker Thread 会中止宏任务队列,不会中止微任务队列。

可以利用以下的代码进行测试:

js
// Main Thread
const worker = new Worker('./worker.js')

worker.addEventListener('message', (event) => {
  console.log('Listen Message from Main Thread')
  // worker.terminate()
})

worker.postMessage('Post Message from Main Thread')
js
// Worker Thread
self.addEventListener('message', (event) => {
  // self.close()
  console.log('Listen Message from Dedicated Worker')
  // timeout
  setTimeout(() => {
    self.postMessage('Timeout Post Message from Dedicated Worker')
  })
  // promise
  Promise.resolve().then(res => {
    self.postMessage('Promise Post Message from Dedicated Worker')
    Promise.resolve().then(res => {
      self.postMessage('Promise2 Post Message from Dedicated Worker')
    })
    setTimeout(() => {
      self.postMessage('Timeout2 Post Message from Dedicated Worker')
    })
  })
  // loop
  for (let i = 0;i <= 10000; i++) {
    if (i === 10000) {
      self.postMessage('Loop Post Message from Dedicated Worker')
    }
  }
})

3-4.importScripts()

在现实开发中,所有逻辑只放在一个 worker 文件内,明显是不便的。

因此,WorkGlobalScope 提供了一个 importScripts 方法,可以在 Worker Thread 中引入其他 worker 文件,从而方便开发者拆分 worker 模块。

我们假设有 Main ThreadWorker Thread 以及 Sub Worker Thread,那么可以这样使用 importScripts

js
// Main Thread
const worker = new Worker('./worker.js', {
  type: 'classic'
})

worker.postMessage('Post Message from Main Thread')

worker.onmessage = (event) => {
  console.log('worker.onmessage', event)
}

worker.onmessageerror = (event) => {
  console.log('worker.onmessageerror', event)
}

worker.onerror = (event) => {
  console.log('worker.onerror', event)
}
js
// Worker Thread
importScripts('./subWorker.js')

self.addEventListener('message', (event) => {
  console.log('dedecated.onmessage', event)
})

getDefaultImportedMessage()
js
// Sub Worker Thread
function getDefaultImportedMessage () {
  const message = 'Default imported message from Sub Worker Thread'
  console.log(message)
  return message
}

如果本例中要引入的 Sub Worker ThreadesModule 规范,那么需要改造以下几点:

  1. Main Thread 中的 type 类型声明为 module

  2. Worker Thread 使用 esModule 规范导入对应文件,不支持 importScripts 导入。Module scripts don't support importScripts()

  3. Sub Worker Thread 中使用 esModule 规范导出模块。

js
// Main Thread
const worker = new Worker('./worker.js', {
  type: 'module'
})

worker.postMessage('postMessage from main thread')

worker.onmessage = (event) => {
  console.log('worker.onmessage', event)
}

worker.onmessageerror = (event) => {
  console.log('worker.onmessageerror', event)
}

worker.onerror = (event) => {
  console.log('worker.onerror', event)
}
js
// Worker Thread
import getDefaultImportedMessage from './subWorker.js'

self.addEventListener('message', (event) => {
  console.log('dedecated.onmessage', event)
})

getDefaultImportedMessage()
js
// Sub Worker Thread
function getDefaultImportedMessage () {
  const message = 'Default imported message from Sub Worker Thread'
  console.log(message)
  return message
}

export {
  getDefaultImportedMessage as default
}

非常重要的一点:

无论是 importScripts() 还是 esModule,本例中的 Sub Worker Thread 的全局域是 DedicatedWorkerGlobalScope,而非 SharedWorkerGlobalWorker

TIP

关于 importScripts 有如下特点:

  1. 引入的子 worker 全局域取决于该 importScripts 的执行全局域。如果在 DedicatedWorkerGlobalScope 中引入另一 worker 文件,则该 worker 文件的全局域是 DedicatedWorkerGlobalScope;如果在 ServiceWorkerGlobalScope 中引入另一 worker 文件,则该 worker 文件的全局域是 ServiceWorkerGlobalScope

  2. importScripts 存在变量提升。即使将 importScripts() 方法放在代码末尾执行,实际结果也是 importScripts() 方法内的文件先执行。