异步调用,真的没有那么可怕!

栏目: JavaScript · 发布时间: 5年前

内容简介:异步编程对于开发新手来说,简直是噩梦般存在,但是又非常重要,非学不可。其实啊,异步调用,并没有那么可怕。所谓的异步,简单来说,就是任务不是一次连续完成的,中间加入了其他的程序运算,等第一阶段准备好了数据,再返回来进行计算。回调函数的名字,callback,这个相信做过前端开发的小伙伴都见到过。javaScript语言对异步编程的实现就是回调函数。

异步编程对于开发新手来说,简直是噩梦般存在,但是又非常重要,非学不可。其实啊,异步调用,并没有那么可怕。

传统方法

  • 回调函数
  • 事件监听
  • 发布/订阅
  • Promise对象

基本概念

异步

所谓的异步,简单来说,就是任务不是一次连续完成的,中间加入了其他的程序运算,等第一阶段准备好了数据,再返回来进行计算。

回调函数

回调函数的名字,callback,这个相信做过前端开发的小伙伴都见到过。javaScript语言对异步编程的实现就是回调函数。

举个例子:

fs.readFile('etc/passwd','utf-8',function(err,data){
    if(err) throw err;
    console.log(data);
});
复制代码

Promise

回调函数本身没有问题,问题在于,出现多个回调函数嵌套上,也就是我们俗称的回调地狱。

let url1 = 'http://xxx.xxx.1';
    let url2 = 'http://xxx.xxx.2';
    let url3 = 'http://xxx.xxx.3';
    $.ajax({
        url:url1,
        error:function (error) {},
        success:function (data1) {
            console.log(data1);
            $.ajax({
                url:url2,
                data:data1,
                error:function (error) {},
                success:function (data2) {
                    console.log(data2);
                    $.ajax({
                        url:url3,
                        data,
                        error:function (error) {},
                        success:function (data3) {
                            console.log(data3);
                        }
                    });
                }
            });
        }
    });
复制代码

这种代码本身逻辑可以实现,问题处在下面几个方面:

  • 代码臃肿。
  • 可读性差。
  • 耦合度过高,可维护性差。
  • 代码复用性差。
  • 容易滋生 bug。
  • 只能在回调里处理异常。

在以前的章节中,我们讲到过Promis函数,他就是典型的解决函数异步问题而产生的。 他是回调函数的新写法。

举个例子:

function request(url,data = {}){
    return new Promise((resolve,reject)=>{
        $.ajax({
            url,
            data,
            success:function (data) {
                resolve(data);
            },
            error:function (error) {
                reject(error);
            }
        })
    });
}

let url1 = 'http://xxx.xxx.1';
let url2 = 'http://xxx.xxx.2';
let url3 = 'http://xxx.xxx.3';
request(url1)
    .then((data)=>{
        console.log(data);
        return request(url2,data)
    })
    .then((data)=>{
        console.log(data);
        return request(url3,data)
    })
    .then((data)=>{
        console.log(data)
    })
    .catch((error)=>{
        console.log(error);
    });

复制代码

相对上面来说,使用Promis对象后,代码结构变得清晰的许多。但,问题是,一眼看上去,多了好多个then,而且,不写注释,很难明白到底是什么意思。

Generator函数

定义:

generator函数是ES6提供的一种异步编程解决方案,语法与传统函数完全不同。 执行Generator函数会返回一个遍历器对象。返回的遍历器对象可以依次遍历Generator函数内部的每一个状态

上面的章节中,我们提到过,Genertor函数,最大的特点,就是可以交出函数的执行权。也就是说,可以暂停执行函数。

function * gen(x) {
  var y  = yield  x + 1;
  return y;
}
var g = gen(1);
g.next(); // {value:2,done:false}
g.next(); // {value:undefined.done:true}
复制代码

执行gen函数,返回的是函数指针,调用指针函数的next方法可以移动内部函数的指针,指向第一个遇到的yield语句。

异步封装

下面看一个使用Generator函数执行一个真实的异步任务。

var fetch = require('node-fetch);
  
  function * gen() {
    var url = 'http://api.github.com';
    var result = yield fetch(url);
    console.log(result.bio);
  }
  
  var g = gen();
  var result = g.next();
  
  result.value.then(function(data) {
    return data.JSON();
  }).then(function(data) {
    g.next(data);
  })
  
复制代码

上面的代码将异步表达的很清晰,但是流程管理并不方便,还的自己调用next方法。

Generator函数自动流程管理

例子:

function * gen() {
  //...
}

var g = gen();
var res = g.next();
while(!res.done){
    console.log(res.value);
    g.next()
}
复制代码

上面代码能自动完成,前提是不适合异步,如果必须保证前一步执行完才能执行后一步,上面的自动执行就不行了。

Thunk函数

如何自动完成异步函数呢?引出我们的主角,thunk函数。

var fs = requier('fs');
var thunkify = require('thunkify');
var readFileThunk = thunkify(fs.readFile);

var gen = function *() {
  var r1 = yield readFileThunk('/etc/fstab');
  console.log(r1.toString());
  var r2 = yield readFileThunk('/etc/shells');
  console.log(r2.toString());
} 
复制代码

上面代码,执行异步操作后(yield 后面的表达式),需要将执行权再返回给Generator函数,我们先手动执行一下。

var g = gen();

var r1 = g.next();
r1.value(function(err,data) {
  if(err) throw err;
  var r2 = g.next(data);
  r2.value(function(err,data) {
    if(err) throw err;
    g.next(data);
  })
})

复制代码

仔细看一下,上面的代码,generator函数执行过程其实就是将同一个回调函数反复传入next方法的value属性。

如何自动完成这个操作呢?

function  run (fn) {
  var gen = fn();
  function next(err,data) {
    var result = gen.next(data);
    if(result.done) return ;
    result.value(next)
  }
  next();
}

function* g() {
  //...
}

run(g);
复制代码

上面代码中的run函数就是一个Generator函数的自动执行器。内部的next函数就是Thunk的回调函数,next先将指针移动到Generator函数的下一步,然后判断Generator函数是否结束, 如果没结束,就将next函数再传入thunk函数,否则直接退出。

这就方便多了。只需将要执行的函数,放入run()函数就可以了。妈妈再也不担心我的异步调用了。

CO

每次都写run函数么?很烦馁~

那就尝试一下CO。

co模块是著名的程序员TJ Holowaychuk于2013年6月发布的一个小工具,用于Generator函数自动执行。

怎么用呢?相当简单了。

var co = require('co');
co(gen);
复制代码

相当简便。而且,co函数返回的是一个Promise对象,可以调用then方法添加回调函数。

co(gen).then(function() {
  console.log('到这函数就都执行完了。')
});
复制代码

为什么co能自动执行呢?

generator函数是一个异步执行容器。它的自动执行需要一中机制,当异步操作有了结果,需要自动交回执行权。

解释一下:

function * gen(x) {
  var y = yield x = 1;
  var z = yield 1+1;
  return y, z;
}
复制代码

当执行到第一个yield的时候,调用next方法,获取到值。再次调用next,继续执行,再次调用这步骤,不用人为调用了。

co模块其实就是将thunk函数和promise对象包装成一个模块。使用的前提是,yield后面的表达式必须是thunk函数或者promise对象。4.0之后的co版本只支持promise对象了。

有时间的同学可以看看co的源码,也很简单的,这里就不做过多的介绍了。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Practical Vim, Second Edition

Practical Vim, Second Edition

Drew Neil / The Pragmatic Bookshelf / 2015-10-31 / USD 29.00

Vim is a fast and efficient text editor that will make you a faster and more efficient developer. It’s available on almost every OS, and if you master the techniques in this book, you’ll never need an......一起来看看 《Practical Vim, Second Edition》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具