异步编程中篇

return timeout(2000);node

}).then(function(){jquery

console.log("fourth");es6

return timeout(2000);ajax

});正则表达式

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

实际上Promise的应用仍是比较多,好比前面讲到的fetch,它就利用了 Promise来实现AJAX的异 步操做:编程

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

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': /1\d{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

相关文章
相关标签/搜索