背景 (书接上回)
- 为了讨好ui妹妹,学习内存泄露,可是吧依旧不得ui妹妹的心。
- 我痛定思痛,终于明白了一个道理,那就是,人间舔狗无数,唯有舔者留其名,只要舔的好,世界你最屌。
- 正当我自我安慰之时,就看到我们leader那个流氓,对ui妹妹一顿谄媚。
- 领导:哇,你这图做的,简约不失大气,灵动不失优雅,高贵不失宁静,简直是国家大帅风采。
- 我:你tm夸人家的时候能别看这腿吗,你好歹看着电脑讲呀。
- ui妹妹:关你屁事。多嘴。
- 我:哇,ui妹妹给我讲话了,好幸福!!!
- 领导:滚一边去,你个小垃圾,你上次的异步加载问题解决了吗,没解决ui就还是我的。
- 我:得嘞,瞧好吧!!
开始学习
Generator
Generator
是ES6
中新增的语法,和Promise
一样,都可以用来异步编程。Generator函数可以说是Iterator接口的具体实现方式。Generator 最大的特点就是可以控制函数的执行。
function*
用来声明一个函数是生成器函数,它比普通的函数声明多了一个*
,*
的位置比较随意可以挨着function
关键字,也可以挨着函数名yield
产出的意思,这个关键字只能出现在生成器函数体内,但是生成器中也可以没有yield
关键字,函数遇到yield
的时候会暂停,并把yield
后面的表达式结果抛出去next
作用是将代码的控制权交还给生成器函数
javascript
function *foo(x) {
let y = 2 * (yield (x + 1))
let z = yield (y / 3)
return (x + y + z)
}
let it = foo(5)
console.log(it.next()) // => {value: 6, done: false}
console.log(it.next(12)) // => {value: 8, done: false}
console.log(it.next(13)) // => {value: 42, done: true}
上面这个示例就是一个Generator函数,我们来分析其执行过程:
- 首先 Generator 函数调用时它会返回一个迭代器
- 当执行第一次 next 时,传参会被忽略,并且函数暂停在 yield (x + 1) 处,所以返回 5 + 1 = 6
- 当执行第二次 next 时,传入的参数等于上一个 yield 的返回值,如果你不传参,yield 永远返回 undefined。此时 let y = 2 * 12,所以第二个 yield 等于 2 * 12 / 3 = 8
- 当执行第三次 next 时,传入的参数会传递给 z,所以 z = 13, x = 5, y = 24,相加等于 42
yield
实际就是暂缓执行的标示,每执行一次next()
,相当于指针移动到下一个yield
位置
总结一下 ,
Generator
函数是ES6
提供的一种异步编程解决方案。通过yield
标识位和next()
方法调用,实现函数的分段执行
遍历器对象生成函数,最大的特点是可以交出函数的执行权
function
关键字与函数名之间有一个星号;- 函数体内部使用
yield
表达式,定义不同的内部状态; next
指针移向下一个状态
这里你可以说说
Generator
的异步编程,以及它的语法糖async
和awiat
,传统的异步编程。ES6
之前,异步编程大致如下
- 回调函数
- 事件监听
- 发布/订阅
传统异步编程方案之一:协程,多个线程互相协作,完成异步任务。
javascript
// 使用 * 表示这是一个 Generator 函数
// 内部可以通过 yield 暂停代码
// 通过调用 next 恢复执行
function* test() {
let a = 1 + 2;
yield 2;
yield 3;
}
let b = test();
console.log(b.next()); // > { value: 2, done: false }
console.log(b.next()); // > { value: 3, done: false }
console.log(b.next()); // > { value: undefined, done: true }
从以上代码可以发现,加上
*
的函数执行后拥有了next
函数,也就是说函数执行后返回了一个对象。每次调用next
函数可以继续执行被暂停的代码。以下是Generator
函数的简单实现
javascript
// cb 也就是编译过的 test 函数
function generator(cb) {
return (function() {
var object = {
next: 0,
stop: function() {}
};
return {
next: function() {
var ret = cb(object);
if (ret === undefined) return { value: undefined, done: true };
return {
value: ret,
done: false
};
}
};
})();
}
// 如果你使用 babel 编译后可以发现 test 函数变成了这样
function test() {
var a;
return generator(function(_context) {
while (1) {
switch ((_context.prev = _context.next)) {
// 可以发现通过 yield 将代码分割成几块
// 每次执行 next 函数就执行一块代码
// 并且表明下次需要执行哪块代码
case 0:
a = 1 + 2;
_context.next = 4;
return 2;
case 4:
_context.next = 6;
return 3;
// 执行完毕
case 6:
case "end":
return _context.stop();
}
}
});
}
async/await
Generator
函数的语法糖。有更好的语义、更好的适用性、返回值是Promise
。
- await 和 promise 一样,更多的是考笔试题,当然偶尔也会问到和 promise 的一些区别。
- await 相比直接使用 Promise 来说,优势在于处理 then 的调用链,能够更清晰准确的写出代码。缺点在于滥用 await 可能会导致性能问题,因为 await 会阻塞代码,也许之后的异步代码并不依赖于前者,但仍然需要等待前者完成,导致代码失去了并发性,此时更应该使用 Promise.all。
- 一个函数如果加上 async ,那么该函数就会返回一个 Promise
async => *
await => yield
javascript
// 基本用法
async function timeout (ms) {
await new Promise((resolve) => {
setTimeout(resolve, ms)
})
}
async function asyncConsole (value, ms) {
await timeout(ms)
console.log(value)
}
asyncConsole('hello async and await', 1000)
下面来看一个使用 await
的代码。
less
var a = 0
var b = async () => {
a = a + await 10
console.log('2', a) // -> '2' 10
a = (await 10) + a
console.log('3', a) // -> '3' 20
}
b()
a++
console.log('1', a) // -> '1' 1
- 首先函数
b
先执行,在执行到await 10
之前变量a
还是0
,因为在await
内部实现了generators
,generators
会保留堆栈中东西,所以这时候a = 0
被保存了下来 - 因为
await
是异步操作,遇到await
就会立即返回一个pending
状态的Promise
对象,暂时返回执行代码的控制权,使得函数外的代码得以继续执行,所以会先执行console.log('1', a)
- 这时候同步代码执行完毕,开始执行异步代码,将保存下来的值拿出来使用,这时候
a = 10
- 然后后面就是常规执行代码了
优缺点:
async/await
的优势在于处理 then 的调用链,能够更清晰准确的写出代码,并且也能优雅地解决回调地狱问题。当然也存在一些缺点,因为 await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低。
async原理
async/await
语法糖就是使用Generator
函数+自动执行器来运作的
scss
// 定义了一个promise,用来模拟异步请求,作用是传入参数++
function getNum(num){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num+1)
}, 1000)
})
}
//自动执行器,如果一个Generator函数没有执行完,则递归调用
function asyncFun(func){
var gen = func();
function next(data){
var result = gen.next(data);
if (result.done) return result.value;
result.value.then(function(data){
next(data);
});
}
next();
}
// 所需要执行的Generator函数,内部的数据在执行完成一步的promise之后,再调用下一步
var func = function* (){
var f1 = yield getNum(1);
var f2 = yield getNum(f1);
console.log(f2) ;
};
asyncFun(func);
- 在执行的过程中,判断一个函数的
promise
是否完成,如果已经完成,将结果传入下一个函数,继续重复此步骤 - 每一个
next()
方法返回值的value
属性为一个Promise
对象,所以我们为其添加then
方法, 在then
方法里面接着运行next
方法挪移遍历器指针,直到Generator
函数运行完成
往期内容:
设计呀,你是真会给前端找事呀!!!
面试官: 你对事件循环的理解很不错!所以我总结了下来。
没用的东西,你连个内存泄漏都排查不出来!!
在去面试的时候,我倒在了数组上!!
扫描关注一下公众号呗:技术、段子我都有