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

栏目: 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的源码,也很简单的,这里就不做过多的介绍了。


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

查看所有标签

猜你喜欢:

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

天涯虚拟社区

天涯虚拟社区

刘华芹 / 民族出版社 / 2005-11 / 23.00元

网络空间很复杂,好多人并不完全了解或者只是了解到一些皮毛。比如说好多人对于见网友一事总是抱着浪漫或者暖昧的想法,而事实却并不总是想象的那样。作者在做虚拟社区研究甚至是在有这个想法之前并不常呆在网上,互联网对于作者来说就是查查资料、收发信年、看看新闻的工具。担是看着越来越多的人把时间花在网上,一处文化上的直觉告诉作者:有一种新的生活方式产生了。强烈的好奇心驱使着作者走到了网上,走到了天涯虚拟社区,并......一起来看看 《天涯虚拟社区》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具