4

JavaScript 并行和串行任务

在 JavaScript 里面 异步 是一个很重要的知识点,JS 的异步跟其他语言不一样, 他是根据执行回调的方式来 实现的。由于我们不知道异步什么时候会执行完,让一组异步任务顺序执行就显得很重要了,这个时候我们就需要 流程控制 ,这种流程控制有两种 串行(series)并行(parallel)

图片描述

实际开发中也许你在 JS 中很少接触到这个 并行串行 ,但是如果你接触过 gulp 我相信你应该对这个概念并不陌生.因为在 gulp 里面编写 task 到处充斥着这种异步执行。在 gulp(4.0)之前,执行任务想要实现这个流程控制只能通过 依赖 来实现或者通过 run-sequence,而在 gulp(4.0)之后官方自己实现了 gulp.seriesgulp.parallel 来实现。如果之前没有接触过 gulp 有兴趣的可以了解下(虽然现在是 Webpack 的世界~~~)

而如果对于 串行并行 有所接触的话,一般都会使用一个叫 nimble,但是我个人对于这个库里面实现 并行 功能是有疑问的.疑问点在于他不是按照我想象的工作方式工作

串行的简单实现

串行的实现有点像 Koa 里面对于中间件的实现。通过一个 next 函数来触发下一个函数的执行。

// 或者使用reduce
let series = function(arr) {
  let index = 0;
  let next = function() {
    if (index >= arr.length) {
      return;
    }
    arr[index](next);
    index++;
  };
  next();
};

这是一个基本的实现逻辑,实现的方式有点粗糙,其实我们每次取出的函数的方式可以进行一个优化,不需要中间变量 index 来记录当前的执行函数,而是通过 Array.prototype.shift 去取数组里面的函数用来执行

let series = function(arr) {
  let next = function(data) {
    let fn = arr.shift();
    if (!fn) {
      return;
    }
    fn(next,data);
  };
  next();
};

// 使用方法
let str = "";
series([
  function(next) {
    setTimeout(function() {
      str = "Hello"
      next(str);
    }, 100);
  },
  function(next,data) {
    setTimeout(function() {
      str += "World"
      console.log(str)
      console.log(data);
      next();
    });
  }
]);

并行的简单实现

刚刚在上面提到过,我对于 nimble 中 并行的实现是有疑问的,他并没有按照我上面的 图片执行,少了我觉得很重要的一步 并行应该又一个最终的函数,这个函数要等其他函数执行完毕才会执行

假设我有三个需要并行执行的函数 fn1,fn2,fn3。 当这三个函数执行之后应该需要在时间最晚的函数内执行一个最终的函数,来 处理一些依赖逻辑。下面是我个人的 简单实现代码

let parallel = function(arr, finnaly) {
  let fn,
    index = 0;
  let statusArr = Array(arr.length)
    .fill()
    .map(() => ({
      isActive: false,
      data: null
    }));
  let isFinished = function() {
    return statusArr.every(item => {
      return item.isActive === true;
    });
  };
  let resolve = function(index) {
    return function(data) {
      statusArr[index].data = data;
      statusArr[index].isActive = true;
      let isFinish = isFinished();
      if (isFinish) {
        let datas = statusArr.map(item => {
          return item.data;
        });
        finnaly(datas);
      }
    };
  };
  while ((fn = arr.shift())) {
    // 给resolve函数追加参数,可以使用bind函数实现,这里使用了柯里化
    fn(resolve(index));
    index++;
  }
};

// 使用方法
let str = "";
parallel(
  [
    function(resolve) {
      setTimeout(function() {
        str = "Hello";
        resolve("Hello");
      }, 1000);
    },
    function(resolve) {
      setTimeout(function() {
        str += "World";
        resolve("World");
      }, 20);
    }
  ],
  function(datas) {
    console.log("finily", datas);
  }
);

关于其他

以上就是简单的 串行并行 实现的基本方案, 有一些地方用到了 ES6,希望读者不要介意。其实如果你是 面向ES6开发 的话。更简单的方式是用 Promise 的方案,甚至可以使用 async/awaitgenerator 这些高级方法,来使 异步 代码跟同步一样使用,使代码的可读性更高

// 并行
let datas = await Promise.all([
  new Promise(resolve => {
    setTimeout(resolve.bind(this,10), 1000);
  }),
  new Promise(resolve => {
    setTimeout(resolve.bind(this,12), 2000);
  })
])

// 串行
let data1 = await new Promise(resolve => {
  setTimeout(resolve.bind(this, 10), 1000);
})
let data2 = await new Promise(resolve => {
  setTimeout(resolve.bind(this, 12), 1000);
});

这样看起来是不是很简洁

ES6 大法好!!!

ES6 大法好!!!

ES6 大法好!!!

重要事情说三遍
相信你看出来了吧,这篇文章其实是来宣传ES6的,不过还是希望大家能了解一下底层的实现。另外有一篇 Promise的实现。写的相当好,极力推荐阅读一下

图片描述


SzHeJason
141 声望4 粉丝