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中,默认支持迭代器的结构有:
•函数的arguments对象
这里面并无包含自定义的对象,因此当咱们建立一个自定义对象后,是没法经过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