什么是回调函数?
当一个函数作为参数传入另一个参数中,并且它不会立即执行,只有当满足一定条件后该函数才可以执行,这种函数就称为回调函数。定时器和Ajax中就存在有回调函数。
定时器:
setTimeout(function () {
console.log("执行回调函数")
}, 3000)
这里的function () {console.log("执行回调函数")}就是回调函数,在3秒后执行。
Ajax:
// 创建异步对象
const xhr = new XMLHttpRequest();
// 请求路径
const url = 'http://localhost:3000/getData';
// 绑定监听事件
xhr.onreadystatechange = function () {
console.log(this.readyState);
// 这个方法会被调用4次,最后一次readyState=4,并且status=200时才是最终想要的响应结果
if (this.readyState == 4 && this.status == 200) {
// 响应数据
const response = xhr.responseText;
console.log(response);
}
}
// 创建请求
xhr.open('GET', url);
// 发送请求
xhr.send();
这里onreadystatechange绑定的函数就是回调函数,在发送请求并拿到响应后执行
什么是回调地狱?
回调地狱是只在异步js里,回调函数写的太多了,回调套回调,导致代码的可读性下降。
需求执行先执行步骤一,然后再执行步骤二,然后在执行步骤三。
回调地狱的写法:
setTimeout(function () {
console.log("步骤一")
setTimeout(function () {
console.log("步骤二")
setTimeout(function () {
console.log("步骤三")
}, 1000)
}, 1000)
}, 1000)
怎么解决回调地狱?
1、使用Promise
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理且更强大。它最早由社区提出并实现,ES6将其写进了语言标准,统一了用法,并原生提供了Promise对象。
- Promise构造函数接收一个函数作为参数,我们需要处理的异步任务就写在该函数体内,该函数的两个参数是resolve,reject。异步任务执行成功时调用resolve函数返回结果,反之调用reject。
- Promise对象的then方法用来接收处理成功时响应的数据,catch方法用来接收处理失败时相应的数据。
- Promise的链式编程可以保证代码的执行顺序,前提是每一次在then做完处理后,一定要return一个Promise对象,这样才能在下一次then时接收到数据。
const getOne = function (id) {
console.log('步骤一:', id);
return new Promise((resolve, reject) => {
resolve({result: true, text: 1});
})
}
const getTwo = function (id) {
console.log('步骤二:', id);
return new Promise((resolve, reject) => {
resolve({result: true, text: 2});
})
}
const getThree = function (id) {
console.log('步骤三:', id);
return new Promise((resolve, reject) => {
resolve({result: true, text: 3});
})
}
getOne('one').then((res) => {
console.log(res);
return getTwo('two')
}).then((res) => {
console.log(res);
return getThree('three')
}).then((res) => {
console.log(res)
}).catch((e) => {
console.log(e);
})
2、使用async/await
它是es8的新特性
async和await结合可以让异步代码像同步代码一样
- async表示这是一个async函数,await必须写在async函数中
- await右侧的表达式一般为promise对象
- await返回的是promise成功的值
- await的promise失败了就会抛出异常,需要通过try…catch捕获处理
- 返回的结果如果不是一个Promise对象(return 字符串,数字类型等,undefined等),则函数返回的结果就是一个成功的Promise对象
- 如果返回的结果是一个promise对象,则成功的promise 的值就是该函数成功的值
const getOne = function (id) {
console.log('步骤一:', id);
return new Promise((resolve, reject) => {
resolve({result: true, text: 1});
})
}
const getTwo = function (id) {
console.log('步骤二:', id);
return new Promise((resolve, reject) => {
resolve({result: true, text: 2});
})
}
const getThree = function (id) {
console.log('步骤三:', id);
return new Promise((resolve, reject) => {
resolve({result: true, text: 3});
})
}
const makeRequest = async () => {
try {
const data1 = await getOne('one');
console.log(data1)
if (data1) {
const data2 = await getTwo('two');
console.log(data2)
if (data2) {
const data3 = await getThree('three');
console.log(data3)
}
}
} catch (e) {
console.log(e)
}
}
makeRequest();
3、使用Generator
Generator是ES6提出的一种异步编程的方案。因为手动创建一个iterator十分麻烦,因此ES6推出了generator,用于更方便的创建iterator。也就是说,Generator就是一个返回值为iterator对象的函数。
Generator(生成器)是一种函数,声明方式和普通函数类似,只不过要在function后面加个*(function*),
Generator(生成器)是一特殊的函数,定义方式和执行方式和普通函数不一样。
- 生成器是一个特殊的函数(在函数名前面加一个*号),用来解决异步编程的回调地狱问题,语法行为和传统函数完全不同。
- yield 语句是函数代码的分隔符,把函数代码切割成几块通过 next() 来控制代码的一个向下的执行。
- 使用 iterator 迭代器对象说明可以使用 for...of 来遍历,每一次调用返回的结果是 yield 后面的内容。
- next 执行的时候是可以传参的,参数将作为上一个 yield 语句整体返回结果。
const getOne = function (id) {
console.log('步骤一:', id);
setTimeout(() => {
iterator.next({result: true, text: 1});
}, 1000);
}
const getTwo = function (id) {
console.log('步骤二:', id);
setTimeout(() => {
iterator.next({result: true, text: 2});
}, 1000);
}
const getThree = function (id) {
console.log('步骤三:', id);
setTimeout(() => {
iterator.next({result: true, text: 3});
}, 1000);
}
function* gen() {
const data1 = yield getOne('one');
console.log(data1)
const data2 = yield getTwo('two');
console.log(data2)
const data3 = yield getThree('three');
console.log(data3)
}
let iterator = gen();
iterator.next();