zoukankan      html  css  js  c++  java
  • 异步编程中篇

    return timeout(2000);

    }).then(function(){

    console.log("fourth");

    return timeout(2000);

    });

    由于需要多次创建Promise对象,所以用了 timeout函数将它封装起来,每次调用它都会返回一个 新的Promise对象。当then方法调用后,其内部的回调函数默认会将当前的Promise对象返回。当 然也可以手动返回一个新的Promise对象。我们这里就手动返回了一个新的计时对象,因为需要 重新开始计时。后面继续用the n方法来触发异步完成的回调函数。这样就可以做到同步的效果, 从而避免了过多的回调嵌套带来的"回调地狱"问题。

    实际上Promise的应用还是比较多,比如前面讲到的fetch,它就利用了 Promise来实现AJAX的异 步操作:

    let pm = fetch("/users"); // 获取Promise对象

    pm.then((response) => response.text()).then(text => {

    test.innerText = text; //将获取到的文本写入到页面上

    })

    .catch(e rror => console.log("出错了 "));

    注意:response.text0返回的不是文本,而是Promise对象。所以后面又跟了一个then, 后从新的Promise对象中获取文本内容。

    Promise作为ES6提供的一种新的异步编程解决方案,但是它也有问题。比如,代码并没有因为 新方法的出现而减少,反而变得更加复杂,同时理解难度也加大。因此它并不是异步实现的最终 形态,后续我们还会继续介绍其他的异步实现方法。

    16-3迭代器与生成器

    上一节中我们学习了如何使用Promise来实现异步操作。但是它也会存在一些问题,比如代码量 增多,不易理解。那么这一节咱们将一起来探索其他解决异步的方法。生成器作为ES6新增加的 语法,它也能够处理异步的操作。不过再讲生成器之前,咱们还得理解另外一个东西:迭代器。

    16-3-1 迭代器(Iterator)

    迭代器是一种接口,也可以说是一种规范。它提供了一种统一的遍历数据的方法。我们都知道数 组、集合、对象都有自己的循环遍历方法。比如数组的循环:

    let ary = [1,2,3,4,5,6,7,8,9,10];

    //for循环

    for(let i = 0;i < ary.length;i++){

    console.log(ary[i]);

    }

    //forEach 循环

    ary.forEach(function(ele){

    console.log(ele);

    });

    //for-in循环

    for(let i in ary){

    console.log(ary[i]);

    }

    //for-of 循环

    for(let ele of ary){

    console.log(ele);

    }

    集合的循环:

    let list = new Set([1,2,3,4,5,6,7,8,9,10]);

    for(let ele of list){

    console.log(ele);

    }

    对象的循环:

    let obj = {

    name : 'tom',

    age : 25,

    gender :'男',

    intro : function(){

    console.log('my name is '+this.name);

    }

    }

    for(let attr in obj){

    console.log(attr);

    }

    从以上的代码可以看到,数组可以用for、forEach、for-in以及for-of来遍历。集合能用for-of。对 象能用for-in。也就是说,以上数据类型的遍历方式都各有不同,那么有没有统一的方式遍历这 些数据呢?这就是迭代器存在的意义。它可以提供统一的遍历数据的方式,只要在想要遍历的数 据结构中添加一个支持迭代器的属性即可。这个属性写法是这样的:

    const obj = {

    [Symbol.iterator]:function(){}

    }

    [Symbol.ite rato r]属性名是固定的写法,只要是拥有该属性的对象,就能够用迭代器的方式 进行遍历。

    迭代器的遍历方法是首先获得一个迭代器的指针,初始时该指针指向第一条数据之前。接着通过 调用n ext方法,改变指针的指向,让其指向下一条数据。每一次的n ext都会返回一个对象,该对 象有两个属性。其中value代表想要获取的数据,done是个布尔值,false表示当前指针指向的数 据有值。true表示遍历已经结束。

    let ary = [1,2,3];

    let it = ary[Symbol.iterator](); // 获取数组中的迭代器

     
       
     
       

    数组是支持迭代器遍历的,所以可以直接获取其中的迭代器。集合也是一样。

    let list = new Set([1,2,3]);

    let it = list.entries(); //获取set集合中的迭代器

    console.log(it.next()); // { value: console.log(it.next()); // { value: console.log(it.next()); // { value: console.log(it.next()); // { value:

    set集合中每次遍历出来的值是一个数组,里面的第一和第二个元素都是一样的。

    由于数组和集合都支持迭代器,所以它们都可以用同一种方式来遍历。es6中提供了一种新的循 环方法叫做f or-of。它实际上就是使用迭代器来进行遍历,换句话说只有支持了迭代器的数据结 构才能使用for-o f循环。在JS中,默认支持迭代器的结构有:

    • Array
    • Map
    • Set
    • String
    • TypedArray

    •函数的arguments对象

    • NodeList 对象

    这里面并没有包含自定义的对象,所以当我们创建一个自定义对象后,是无法通过for-of来循环 遍历它。除非将iterator接口加入到该对象中:

    let obj = { name: 'xiejie', age: 18,

    gende r:'男', intro: function () { console.log('my name is ' + this.name);

    },

    [Symbol.iterator]: function () {

    let i = 0;

    let keys = Object.keys(this); //获取当前对象的所有属性并形成一个数组 return {

    next: function () {

    return {

    value: keys[i++], //外部每次执行next都能得到数组中的第i个元素 done: i > keys.length //如果数组的数据已经遍历完则返回true }

    }

    }

    }

    }

    for ( let attr of obj) { console.log(attr);

    // name

    // age

    // gender

    // intro

    }

     
       
     
       

    let it = obj[Symbol.iterator]();

     

    通过自定义迭代器就能让自定义对象使用for-of循环。迭代器的概念及使用方法我们清楚了, 接下来就是生成器。

    16-3-2 生成器(Generator)(扩展)

    生成器也是ES6新增加的一种特性。它的写法和函数非常相似,只是在声明时多了一个*号。

    function* say(){}

    const say = function*(){}

    |注意:这个*只能写在function关键字的后面。

    生成器函数和普通函数并不只是一个*号的区别。普通函数在调用后,必然开始执行该函数,直 到函数执行完或遇到return为止。中途是不可能暂停的。但是生成器函数则不一样,它可以通过 yield关键字将函数的执行挂起,或者理解成暂停。它的外部在通过调用n ext方法,让函数继续执 行,直到遇到下一个yield,或函数执行完毕。

    function* say(){ yield "开始";

    yield "执行中"; yield "结束";

    }

    let it = say(); //调用say方法,得到一个迭代器

     
       
     
       

    调用say函数,这句和普通函数的调用没什么区别。但是此时say函数并没有执行,而是返回了一 个该生成器的迭代器对象。接下来就和之前一样,执行next方法,say函数执行,当遇到yield
    时,函数被挂起,并返回一个对象。对象中包含value属性,它的值是yield后面跟着的数据。并 done的值为false。再次执行next,函数又被激活,并继续往下执行,直到遇到下一个yield。

    当所有的yield都执行完了,再次调用next时得到的value就是undefined, done的值为true。

    如果你能理解刚才讲的迭代器,那么此时的生成器也就很好理解了。它的y ield,其实就是n ext 法执行后挂起的地方,并得到你返回的数据。那么这个生成器有什么用呢?它的y ield关键字可以 将执行的代码挂起,外部通过next方法让它继续运行。

    这和异步操作的原理非常类似,把一个操作分为两部分,先执行一部分,然后再执行另外一部 分。所以生成器可以处理和异步相关的操作。我们知道,异步操作主要是依靠回调函数实现。但 是纯回调函数的方式去处理同步效果会带来“回调地域“的问题。Promise可以解决这个问题。但 Promise写起来代码比较复杂,不易理解。而生成器又提供了一种解决方案。看下面这个例 子:

    function* delay() {

    yield new Promise((resolve, reject) => { setTimeout(() => { resolve() }, 2000) })

    console.log("go on");

    }

    let it = delay(); //得到一个迭代器

    // it.next()会执行到第一个 yield 得到的值为{ value: Promise { vpending> }, done: false }

    // it.next().value 将会得到_ Promise

    // Promise会在2秒以后调用then方法

    // 2秒后调用then方法执行迭代器的下一步

    it.next().value.then(() => {

    it.next();

    });

    这个例子实现了等待2秒钟后,打印字符串"go on"。下面我们来分析下这段代码。在delay这个 生成器中,yield后面跟了一个Promise对象。这样,当外部调用next时就能得到这个Promise 象。然后调用它的then函数,等待2秒钟后Promise中会调用resolve方法,接着the n中的回调函 数被调用。也就是说,此时指定的等待时间已到。然后在the n的回调函数中继续调用生成器的 next方法,那么生成器中的代码就会继续往下执行,最后输出字符串"go on"。

    例子中时间函数外面为什么要包裹一个Promise对象呢?这是因为时间函数本身就是一个异步方 法,给它包裹一个Promise对象后,外部就可以通过the n方法来处理异步操作完成后的动作。这 样,在生成器中,就可以像写同步代码一样来实现异步操作。比如,利用fetch来获取远程服务器 的数据(为了测试方便,我将用M ockJS来拦截请求)。

    <body>

    <script src="./jquery-1.12.4.min.js"></script>

    <script src="./mock-min.js"></script>

    <script>

    //拦截Ajax请求

    Mock.mock(/.json/, {

    'stuents|5-10': [{

    'id|+1': 1,

    'name': '@cname',

    'gende r': /[男女]/, //在正则表达式匹配的范围内随机

    'age|15-30': 1, //年龄在15-30之间生成,值1只是用来确定数据类型 'phone': /1d{10}/,

    'add r': '@county(t rue)', //随机生成中国的一个省、市、县数据

    'date': "@date('yyyy-MM-dd')"

    }]

    });

    function* getUsers() {

    let data = yield new Promise((resolve, reject) => {

    $.ajax({

    type: "get",

    url: "/users.json",

    success: function (data) {

    resolve(data)

    }

    });

    });

    console.log("得到的 data 为:",data);

    }

    let it = getUse rs(); // 返回一个迭代器

    // it.next().value会得到一个Promise, Promise里面向服务器发送请求获取数据

    //数据获取成功以后调用then方法,并将获取到的数据传递给then方法

    // then方法里面再次开启迭代器,执行第二句代码,并将数据传递过去 //在getUse rs函数里面data变量接收了传递过来的数据,并打印出来 it.next().value.then((data) => {

    it.next(data);

    });

    </script>

    </body>

    在Promise中调用JQuery的AJAX方法,当数据返回后调用resolve,触发外部then方法的回调函 数,将数据返回给外部。外部的the n方法接收到data数据后,再次调用n ext,移动生成器的指 针,并将data数据传递给生成器。所以,在生成器中你可以看到,我声明了一个data变量来接收 异步操作返回的数据,这里的代码就像同步操作一样,但实际上它是个异步操作。当异步的数据 返回后,才会执行后面的打印操作。这里的关键代码就是y ield后面一定是一个Promise对象,因 为只有这样外部才能调用the n方法来等待异步处理的结果,然后再继续做接下来的操作。

    之前我们还讲过一个替代AJAX的方法fetch,它本身就是用Promise的方法来实现异步,所以代 码写起来会更简单:

    function* getUsers(){

    let response = yield fetch("/users");

    let data = yield response.json();

    console.log("data",data);

    }

    let it = getUsers();

    it.next().value.then((response) => {

    it.next(response).value.then((data) => {

    it.next(data);

    });

    });

    |由于mock无法拦截fetch请求,所以我用nodejs+express搭建了一个mock-server服务器。

    这里的生成器我用了两次yield,这是因为f etch是一个异步操作,获得了响应信息后再次调用json 方法来得到其中返回的JSO N数据。这个方法也是个异步操作。

    从以上几个例子可以看出,如果单看生成器的代码,异步操作可以完全做的像同步代码一样,比 起之前的回调和Promise都要简单许多。但是,生成器的外部还是需要做很多事情,比如需要频 繁调用n ext,如果要做同步效果依然需要嵌套回调函数,代码依然很复杂。市面也有很多的插件 可以辅助我们来执行生成器,比如比较常见的co模块。它的使用很简单:

    co(getUsers);

    引入co模块后,将生成器传入它的方法中,这样它就能自动执行生成器了。关于co模块这里我就

    不再多讲,有兴趣的话可以参考这篇文章:http://es6.ruanyifeng.eom/#docs/generator-async

  • 相关阅读:
    【HDOJ6667】Roundgod and Milk Tea(模拟)
    【HDOJ6655】Just Repeat(贪心)
    【HDOJ6651】Final Exam(贪心)
    【HDOJ6646】A + B = C(模拟)
    【HDOJ6656】Kejin Player(期望DP)
    【2019 Multi-University Training Contest 7】
    【HDOJ6635】Nonsense Time(时间倒流,lis)
    【HDOJ6638】Snowy Smile(线段树)
    【HDOJ6641】TDL(数论)
    【HDOJ6645】Stay Real(堆)
  • 原文地址:https://www.cnblogs.com/jrzqdlgdx/p/11350735.html
Copyright © 2011-2022 走看看