Generator 函数
基本概念
Generator 译名生成器,是一种生成遍历器的函数。生成器函数与普通函数相比,有两个特征:一是,function关键字与函数名之间有一个星号;二是,函数体内部使用 yield 表达式。
例子:
function* fn() {
yield 0;
yield 1;
}
*不能使用箭头函数创建生成器函数。
调用生成器函数后,创建的是一个遍历器对象,每次调用遍历器的 next() 方法,就会执行一条 yield 语句,然后自动停止执行,直到再次调用 next() 方法才会继续执行。 next() 方法返回对象的 value 属性(当前 yield 表达式后面的任何值或表达式计算结果),以及 done 属性(false,表示遍历还没有结束)。
例子:
function* fn() {
yield 0;
yield 1;
};
//创建遍历器
let iterator = fn();
console.log(iterator.next()); //输出:{value: 0, done: false}
console.log(iterator.next()); //输出:{value: 1, done: false}
console.log(iterator.next()); //输出:{value: undefined, done: true}
console.log(iterator.next()); //输出:{value: undefined, done: true}
*第 3 次调用 next() 方法,返回对象的 value 属性为 undefined,done 属性为 true,说明遍历器已经运行完毕,以后再调用 next() 方法,返回的都是这个值。
next() 方法可以带一个参数,该参数会覆盖上一个 yield 表达式的返回值。
例子:
function* fn(x) {
let y = yield x + 1; //x=1 => 1+1
let z = yield y + 1; //y=3 => 3+1
return x + y + z; //x=1,y=3,z=4 => 1+3+4
}
let iterator = fn(1);
console.log(iterator.next()); // 输出:{value: 2, done: false}
console.log(iterator.next(3)); // 输出:{value: 4, done: false}
console.log(iterator.next(4)); // 输出:{value: 8, done: true}
遍历器中的代码执行过程示意图:
*第一次调用 next() 方法时传入任何参数都没有效果,因为 next() 方法的参数会替代上一次 yield 表达式的返回值,而第一次调用 next() 方法前并没有执行任何 yield 语句。
遍历器的常用方法
return() 方法
return() 方法将yield 表达式替换成一个return语句,并返回给定的值。
例子:
let fn = function*() {
yield 1;
yield 2;
yield 3;
}
let iterator = fn();
console.log(iterator.next()); //输出:{value: 1, done: false}
console.log(iterator.return(9)); //输出:{value: 9, done: true}
console.log(iterator.next()); //输出:{value: undefined, done: true}
throw() 方法
throw() 将 yield 表达式替换成一个 throw 语句,并返回给定的错误信息。
例子:
let fn = function*() {
yield 1;
yield 2;
yield 3;
}
let iterator = fn();
console.log(iterator.next()); //输出:{value: 1, done: false}
console.log(iterator.throw(new Error('出错了!'))); //输出:Error: 出错了!
onsole.log(iterator.next()); //没有输出
yield*
表达式
yield* 表达式用来在一个生成器函数里面,遍历拥有遍历器接口(Symbol.iterator)的数据结构。
例子:
数组
let fn = function*() {
yield 1;
yield*[2, 3];
yield 4;
}
let iterator = fn();
console.log(iterator.next()); //输出:{value: 1, done: false}
console.log(iterator.next()); //输出:{value: 2, done: false}
console.log(iterator.next()); //输出:{value: 3, done: false}
console.log(iterator.next()); //输出:{value: 4, done: false}
console.log(iterator.next()); //输出:{value: undefined, done: true}
字符串
let fn = function*() {
yield*"love";
}
let iterator = fn();
console.log(iterator.next()); //输出:{value: "l", done: false}
console.log(iterator.next()); //输出:{value: "o", done: false}
console.log(iterator.next()); //输出:{value: "v", done: false}
console.log(iterator.next()); //输出:{value: "e", done: false}
console.log(iterator.next()); //输出:{value: undefined, done: true}
遍历器对象
let fn1 = function*() {
yield 2;
yield 3;
}
let fn2 = function*() {
yield 1;
yield* fn1();
yield 4;
}
let iterator = fn2();
console.log(iterator.next()); //输出:{value: 1, done: false}
console.log(iterator.next()); //输出:{value: 2, done: false}
console.log(iterator.next()); //输出:{value: 3, done: false}
console.log(iterator.next()); //输出:{value: 4, done: false}
console.log(iterator.next()); //输出:{value: undefined, done: true}
生成器函数与异步编程
生成器函数与 Promise 对象结合使用,可以达到一种书写形式上的“化异步为同步”的效果。
假如现在有两个请求,第二个请求的参数是第一个请求的返回值,首先看下面Promise 异步编程的传统写法:
例子:
//异步请求数据的函数
function p1(time) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(1);
}, time);
})
};
//p2 接收到 p1 的数据才能进行下一次异步请求
function p2(n, time) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(n + 1);
}, time);
})
};
//Promise 的链式编写模式
p1(1000).then((data) => {
console.log(data);
return p2(data, 1000);
}).then((data) => {
console.log(data);
return "end";
}).then((data) => {
console.log(data)
});
//输出:1 2 end
由于生成器函数的暂停执行的效果:yield 语句会暂停当前函数的执行,并且等待下一次调用 next()方法。这意味着可以把第一个请求写在 yield 关键字后面,待取得数据之后再一次调用 next()方法,进行第二次请求。
例子:
//异步请求数据的函数
function p1(time) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(1);
}, time);
})
};
//p2 接收到 p1 的数据才能进行下一次异步请求
function p2(n, time) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(n + 1);
}, time);
})
};
//使用生成器函数的编写模式
function* iterator() {
//第一次请求
let data = yield p1(1000);
//第二次请求
yield p2(data, 1000);
console.log("end");
};
//手动执行生成器函数
let it = iterator();
it.next().value.then((data) => {
console.log(data);
it.next(data).value.then((data) => {
console.log(data);
it.next();
})
});
//输出:1 2 end
上面的代码中,使用生成器函数编写的代码,类似于同步任务的书写方式:第一次请求的返回值会为变量 data 赋值,而接下来会带着已经赋值的变量 data 发出第二次请求。这种书写方式的好处是逻辑很清晰。 上面的代码中,我们手动编写了执行生成器函数的过程,我们也可以写一个函数,自动去执行生成器函数:
例子:
//异步请求数据的函数
function p1(time) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(1);
}, time);
})
};
//p2 接收到 p1 的数据才能进行下一次异步请求
function p2(n, time) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(n + 1);
}, time);
})
};
//使用生成器函数的编写模式
function* iterator() {
//第一次请求
let data = yield p1(1000);
//第二次请求
yield p2(data, 1000);
console.log("end");
};
//自动执行生成器函数
function run(iterator) {
let it = iterator();
function next(data) {
var result = it.next(data);
if (!result.done) {
result.value.then((data) => {
console.log(data);
next(data);
})
}
}
next();
};
run(iterator);
async 函数
基本概念
Generator 函数(生成器)并非解决异步编程的完美方案,最大的缺点是必须手动调用 next() 方法一步一步的去执行,或者依赖外部文件(co 函数库等)去执行。而 async 函数就是将 Generator 函数和自动执行器,包装在一个函数里。
基本用法
在形式上,async 函数将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await。
在功能上,async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。async 函数中,return 对应着 Promise 对象中 resolve 函数,throw 对应着 Promise 对象中 reject 函数。
例子:
async function _Async(n) {
if (n > 5) {
return ">5" //相当于 resolve(">5")
} else {
throw "<5" //相当于 reject("<5")
}
}
_Async(6).then(function(data) {
console.log(data); //输出:>5
});
_Async(4).catch(function(data) {
console.log(data); //输出:<5
});
*可以使用箭头函数创建 async 函数。
上例中并不能体现出 async 函数在异步编程中的优势,async 函数还需要结合 await 关键字一起使用。 await 会暂停当前 async 函数的执行,等待后面的 Promise 的计算结果返回以后再继续执行当前的 async 函数。 如果说 Generator 函数与 Promise 组合使用,是对 Promise 异步编程的改善,那么 async/await 是对这一组合的又一次升级。
例子:
//异步请求数据的函数
function p1(time) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(1);
}, time);
})
};
//p2 接收到 p1 的数据才能进行下一次异步请求
function p2(n, time) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(n + 1);
}, time);
})
};
//async 函数
async function _Async(time) {
let data = await p1(time).then(val => {
console.log(val);
return val;
});
await p2(data, time).then(val => {
console.log(val);
});
return "end";
};
_Async(1000).then(val => {
console.log(val);
});
//输出:1 2 end
正常情况下,await 命令后面是一个 Promise 对象,如果是其他的异步操作(例如 setTimeout),那它还是会继续向下执行,不会等待;如果是基本类型数据,就直接返回对应的值。
例子1:
其他异步操作
let _Async = (async function(time) {
await setTimeout(() => {
console.log(1)
}, time);
console.log(2);
})(1000);
//输出:2 1
例子2:
基本类型数据
let _Async = async function() {
console.log(1);
return await 2; //等同于 return 2
};
_Async().then(v => {
console.log(v);
});
//输出:1 2
如有错误,欢迎指正,本人不胜感激。
热门评论
字母漏打了