1、回调函数
先来一个回调函数简单的例子
ajax(url, () => {
// 处理逻辑
})
2、地狱回调
但是回调函数存在一个问题,那就是容易写出地狱回调(Callback hell)。假设很多个请求存在依赖性,就可能会导致这样的结果
ajax(url, () => {
// 处理逻辑
ajax(url1, () => {
// 处理逻辑
ajax.(url2, () =>{
// 处理逻辑
})
})
})
以上代码看起来不利于阅读和维护,想要利于阅读,可能会这样写
function firstAjax() {
ajax(url1, () => {
// 处理逻辑
})
};
function secondAjax() {
ajax(url2, () => {
// 处理逻辑
})
};
ajax(url, () => {
// 处理逻辑
firstAjax()
});
看上去是利于阅读了,但还是没有解决根本问题。
回调地狱的根本问题是:
- 嵌套的函数存在耦合性,一旦有所改动,就会牵一发而动全身
- 嵌套的函数一多,就很难处理错误
回调函数还存在其它几个缺点,比如不能使用 try catch 捕获错误,不能直接 return 。
3、使用 Generator 解决地狱回调问题
Generator 顾名思义就是一个生成器,使用 function* 语法和一个或多个 yield 表达式以创建一个函数即为生成器,当然它的返回值就是一个迭代器即生成器,Generator 最大的特点就是可以控制函数的执行。
先看一个简单的例子
function *foo(x) {
let y = yield x + 2;
return y;
};
Generator 函数和普通函数的区别在于前者是可以暂停执行的,所以函数名前加 * 号以示区分,其实整个 Generator 函数是一个封装的异步任务,异步操作需要暂停的地方,都用 yield 语句注明。
再来看一段代码
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 函数和普通函数不同,它会返回一个迭代器
- 当执行第一次 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
这样,经过上述的分析,大概就知道了怎么通过 Generator 函数来解决地狱回调的问题,可以把之前地狱回调的例子改写成如下代码
function *fetch() {
yield ajax(url, () => {});
yield ajax(url1, () => {});
yield ajax(url2, () => {});
};
let it = fetch();
let result1 = it.next();
let result2 = it.next();
let result3 = it.next();
4、使用 Promise 解决地狱回调问题
ajax(url)
.then(res => {
console.log(res)
return ajax(url1)
}).then(res => {
console.log(res)
return ajax(url2)
}).then(res => console.log(res))
Promise 实现了链式调用,也就是说每次调用 then 之后返回的都是一个Promise,并且是一个全新的 Promise,原因也是因为状态不可变。如果在 then 中使用了 return,那么 return 的值会被 Promise.resolve() 包装。
---END---