ES6 Generator Function 解救异步编程深度嵌套问题

之前在百度前端学院遇到一道题:

实现快速排序的可视化

当时的实现要么是

  • 深度递归嵌套
  • 把中间结果保存在中间数组中,全部执行完之后输出到dom

第一种实现方式逻辑难度太大,第二种实现方式有点不地道。

但是最新一不小心看到ES6的 Generator Function之后,就可以写简单顺序代码却可以实现异步编程啦。

0x00 实现一个异步编程demo

JavaScript 的执行环境是单线程的,所谓的单线程就是一次只能完成一个任务,其任务的调度方式就是排队,在事件队列中加一个延时,指定自己执行成功需要的时间。

下面代码实现了一个简单的异步编程的方式。

var Q = {
    // 保存队列信息
    a: [],
    // 添加到队列 queue
    q: function(d){
        // 添加到队列如果不是函数或者数字则不处理
        if(!/function|number/.test(typeof d)) return;

        Q.a.push(d);
        // 返回对自身的引用
        return Q;
    },
    // 执行队列 dequeue
    d: function(){
        var s = Q.a.shift();
        // 如果已经到了队列尽头则返回
        if(!s) return;

        // 如果是函数,直接执行,然后继续 dequeue
        if(typeof s === "function") {
            s(), Q.d();
            return;
        }

        // 如果是数字,该数字作为延迟时间,延迟 dequeue
        setTimeout(function(){
            Q.d();
        }, s);
    }
};

使用:

// 进程记录函数
function record(s){
    var div = document.createElement("div");
    div.innerHTML = s;
    console.log(s);
    document.body.appendChild(div);
}

Q
.q(function(){
    record("0 <i style='color:blue'>3s 之后搞定,0 把 1 叫进来</i>");
})
.q(3000)  // 延时 3s
.q(function(){
    record("1 <i style='color:blue'>2s 之后搞定,1 把 2 叫进来</i>");
})
.q(2000)  // 延时 2s
.q(function(){
    record("2 <span style='color:red'>后面没人了,OK,厕所关门~</span>");
})
.d();     // 执行队列

实现了一个简单的异步编程啦。

0x01 浅析javascript中的几种异步编程的方式

1. setTimeout 函数

缺点:setTimeout 是存在一定时间间隔的,并不是设定 n 毫秒执行,他就是 n 毫秒执行,可能会有一点时间的延迟(2ms左右)。

2. ajax 或者 插入节点 的 readyState 变化

这是数据传输方式。ajax在open时设置async为true的时候,也就是异步加载,当 state 发生改变的时候,xhr 立即响应,触发相关的函数。

3.高阶函数篡改回调函数

高阶函数篡改回调函数的方法也可以实现异步编程。

假设需要等待所有异步回调执行完成后,才能执行某个逻辑,这时通过高阶函数篡改回调函数的方式就大为受用,也相当简单。

例子:

var pending = (function () {
  var count = 0;
  return function (callback) {
    count++;
    return function () {
      count--;
      if (count === 0) {
        callback();
      }
    };
  };
}());

var done = pending(function () {
  console.log('all is over');
});

fs.readFile('file1.txt', 'utf8', done());
fs.readFile('file2.txt', 'utf8', done());

总觉得高阶函数代码逻辑会非常混乱。或者说我智商驾驭不了吧。

4 事件监听

JS 和 浏览器提供的原生方法基本都是基于事件触发机制的,耦合度很低,不过事件不能得到流程控制。

5 观察者模式

发布/订阅( Pub/Sub )
把事件全部交给 控制器管理,可以完全掌握事件被订阅的次数,以及订阅者的信息,管理起来特别方便。

6 Promise 对象(deferred 对象)

这不就是iqianyiduanshijian分析的jQuery全局使用的defered模型嘛。
在Promises/A规范中,每个任务都有三种状态:默认(pending)、完成(fulfilled)、失败(rejected)。

默认状态可以单向转移到完成状态,这个过程叫resolve,对应的方法是deferred.resolve(promiseOrValue);
默认状态还可以单向转移到失败状态,这个过程叫reject,对应的方法是deferred.reject(reason);
默认状态时,还可以通过deferred.notify(update)来宣告任务执行信息,如执行进度;
状态的转移是一次性的,一旦任务由初始的pending转为其他状态,就会进入到下一个任务的执行过程中。

7 Web worker

JavaScript 中的 Worker 对象可以创建一个独立线程来处理数据,很自然的处理了阻塞问题。我们可以把繁重的计算任务交给 Worker 去倒腾,等他处理完了再把数据 Post 过来。

var worker = new Worker("./outer.js");
worker.addEventListener("message", function(e){
    console.log(e.message);
});
worker.postMessage("data one");
worker.postMessage("data two");

// outer.js
self.addEventListener("message", function(e){
    self.postMessage(e.message);
});

0x02 ES6 Generator Function

1.generator function

var compute = function* (a, b) {
  var sum = a + b;
  yield console.log(sum);
  var c = a - b;
  yield console.log(c);
  var d = a * b;
  yield console.log(d);
  var e = a / b;
  console.log(e);
};

function* (a, b) 这里*代表是一个generator函数。generator自带一个next()方法。yield代表函数中每一个断点,generator函数在运行到断点时停止,每一次调用next函数向后执行到下一个断点,实现了函数内部的任务拆解。

使用:

var generator = compute(4, 2);
generator.next(); // 6
generator.next(); // 2
generator.next(); // 8
generator.next(); // 2

yield还有一个特性。看下面代码:

var compute = function* (a, b) {
  var foo = yield a + b;
  console.log(foo);
};

var generator = compute(4, 2);
generator.next();//undefined
generator.next("Hello world!"); // Hello world!

我们调用第一次next时,foo此时声明未赋值,输出undefined。
第二次调用next时,传入的hello world可以赋值给yield的变量。
利用这个特性,竟然神奇的在一个函数中停下,传入了参数,对函数的发展做了更改。

2.generator与异步编程

var readFile = helper(fs.readFile);
var flow = function* () {
  var txt = yield readFile('file1.txt', 'utf8');
  console.log(txt);
};
var generator = flow();
var ret = generator.next(); // 执行readFile('file1.txt', 'utf8');

上面代码成功执行读了一个文件

var generator = flow();
var ret = generator.next(); // 执行readFile('file1.txt', 'utf8');
ret.value(function (err, data) {
  if (err) {
    throw err;
  }
  generator.next(data);
});

稍微修改,发现我们可以在读入每个文件之后,拿到中间结果,对错误进行处理,再读入下个文件。

3.添加流程控制的Generator

网上大神的一段精简的代码,完美实现顺序控制复杂异步编程!

var co = function (flow) {
  var generator = flow();
  var next = function (data) {
    var result = generator.next(data);
    if (!result.done) {
      result.value(function (err, data) {
        if (err) {
          throw err;
        }
        next(data);
      });
    }
  };
  next();
};

调用示例:

co(function* () {
  var txt = yield readFile('file1.txt', 'utf8');
  console.log(txt);
  var txt2 = yield readFile('file2.txt', 'utf8');
  console.log(txt2);
});

运行结果:

I am file1.

I am file2.

貌似还是没觉得有多强大,大牛的扩充版本如下:

4.扩展的并行执行的Generator

var co = function (flow) {
  var generator = flow();
  var next = function (data) {
    var ret = generator.next(data);
    if (!ret.done) {
      if (Array.isArray(ret.value)) {
        var count = 0;
        var results = [];
        ret.value.forEach(function (item, index) {
          count++;
          item(function (err, data) {
            count--;
            if (err) {
              throw err;
            }
            results[index] = data;
            if (count === 0) {
              next(results);
            }
          });
        });
      } else {
        ret.value(function (err, data) {
          if (err) {
            throw err;
          }
          next(data);
        });
      }
    }
  };
  next();
};

调用方式:

co(function* () {
  var results = yield [readFile('file1.txt', 'utf8'), readFile('file2.txt', 'utf8')];
  console.log(results[0]);
  console.log(results[1]);
  var file3 = yield readFile('file3.txt', 'utf8');
  console.log(file3);
});

运行结果:

I am file1.

I am file2.

I am file3.

参考:
javascript异步编程原理
Generator与异步编程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值