如何优雅的去做异步处理

124 阅读2分钟

异步

异步和 同步的概念,这边就不去做 阐述了。 无非js的 执行时 单线程的,所以 js的 虚拟机在 执行上 维护这两个 队列,我们 熟知的 宏队列 和 微队列。 宏队列有 setTimeout, setInterval 等, 微任务有 Promise的 callback(then, catch), queueMicrotask. 二者 交替顺序就是 先宏任务 =》 微任务 =》 宏任务。。。 下面就简单 过一下 执行顺序, log 的就是执行顺序

// 注册宏任务
setTimeout(() => {
  console.log(6)
})
console.log(1)
// 注册微任务
Promise.resolve().then(() => {
  console.log(3)
})
new Promise(r => {
  // 本质上是 同步的
  console.log(2)
  r()
}).then(() => {
  console.log(4)
  // 这里注意一下,这里注册了就会马上执行
  Promise.resolve().then(() => {
    console.log(5)
  })
})

但是 => WebWorker

在我们 有大量运算计时 或者 请求耗时上,往往是 用异步的 形式去做处理,比如用 Promise 包一层, 或者直接 Promise.resolve() 去让计算 放到微任务中,防止阻止UI的 绘制。 但是本质上 依旧 是当前线程 去做。 例如 在 前一个微任务的执行就是 阻塞 影响下一个 微任务执行-。-

所以这个时候 就只能 新开一个 线程 去做 WebWorker。 具体介绍就看 MDN

具体用法就是 new Worker 来开辟新 线程进行处理。 但是 该构造函数 只支持 URL, 但是看了 MDN 也是支持 BlobUrl 的,所以,就可以这样写。

// 使用 URL.createObjectURL 去创建 blob url, url返回的就是 我们js 代码
URL.createObjectURL(new Blob(['console.log(123)']))

使用 URL.createObjectURL 去创建 blob url, url返回的就是 我们js 代码

所以思路就很清楚了

const runInAsync = fn => {
  return new Promise((res, rej) => {
    // 判断是否支持 WebWorker 不支持就乖乖用 Promise 异步去做吧
    if (typeof Worker === 'undefined') {
      Promise.resolve().then(() => {
        res(fn())
      })
      
    } else {
      // 这个 Worker 就是 postMessage 回来真正的 执行
      const worker = new Worker(
        URL.createObjectURL(new Blob([`postMessage((${fn})())`]))
      )
      worker.onmessage = ({data}) => {
        res(data)
        worker.terminate()
      }
      worker.onerror = err => {
        rej(err)
        worker.terminate()
      }
    }
  })
}

以上的 runInAsync 就是 真的的异步 执行,是会优先开辟线程 Worker去做的。不会影响到 别的任务执行

测试

我们先写一个 复杂 运算的 function -。-

// 用三个 for 去做循环
const longRunningFunction = () => {
  let result = 0;
  for (let i = 0; i < 1000; i++)
    for (let j = 0; j < 700; j++)
      for (let k = 0; k < 1000; k++) result = result + i + j + k;
  
  console.log(result)
  return result;
};

然后去执行 如下,所以,本质上 还是会阻塞掉 我们的 第二个微任务

Promise.resolve().then(() => {
  longRunningFunction()
})
Promise.resolve().then(() => {
  console.log('micro done')
})
console.log('done')

使用 runInAsync。 因为本质上是 用了另一个线程去做,所以,当然不会阻塞啦

runInAsync(longRunningFunction).then(data => {
  console.log('longRunningFunction invoke data:', data)
})
Promise.resolve().then(() => {
  console.log('micro done')
})
console.log('done')

结语

本质 上 东西很简单, 是WebWorker 的一个简单应用, 无非使用了 BlobUrl 的一种形式去 包裹一层我们的真正执行代码,来巧妙的 去使用 WebWorker。 算了 开阔了一种思路吧。 So. Just have a Good Day!