回调函数
概念:将一个函数当做参数传入另一个函数的时候,这个函数就叫回调函数。
我们之前用过很多次回调函数,比如:数组方法map、filter等;运动函数中处理运动结束传入的函数;分页插件中使用插件的时候执行的函数。。。包括封装的ajax中,请求成功以后执行的success函数。都是回调函数。
为什么要使用回调函数?
当我们执行一些异步操作的时候,需要在操作完成以后,做另外的一些事情,但是我们又没有办法预知这个异步操作什么时候结束,此时只能使用回调函数的形式来解决这个问题。
回调地狱
我们在封装ajax的时候,发现:请求成功后的值不能直接返回给调用者,而需要在其内部执行一个回调函数。如果在请求一次后需要再次请求,那么,也就是在回调函数中需要再次调用ajax,再次传入回调函数,次数多了以后,代码是下面这个样子:
回调地狱 |
---|
![]() |
这样的代码难以阅读和维护,这是著名的”回调地狱“。
ajax({
url: 'http://localhost:8888/test/first',
dataType: 'text',
success: res => {
console.log(res);
// 测试1结束
// 发送测试请求2
ajax({
url: 'http://localhost:8888/test/second',
success: res => {
console.log(res);
// 测试2结束
// 发送测试请求3
ajax({
url: 'http://localhost:8888/test/third',
data: {
name: '张三',
age: 12
},
success: res => {
console.log(res);
}
})
}
})
}
})
这种嵌套多了以后,就会形成回调地狱。
es6为了解决这个问题,所以新增了promise语法。
Promise
promise是承诺的意思,表示他承诺帮你做这件事情,然后将结果给你。
语法:
new Promise(function (resolve, reject) {
// resolve 表示成功的回调
// reject 表示失败的回调
}).then(function (res) {
// 成功的函数
}).catch(function (err) {
// 失败的函数
})
在promise构造函数中,提供了两个参数,分别表示执行成功和失败的回调函数,执行成功调用resolve,失败调用reject即可,具体resolve和reject的执行,分别在then和catch中。
这样可以将回调函数变成链式结构,从而解决了回调地狱的问题。
例:
var p = new Promise(function(resolve, reject){
// 测试请求1
ajax({
url: 'http://localhost:8888/test/first',
dataType: 'text',
success: res => {
resolve(res)
},
error: res => {
reject('请求1错误')
}
})
})
p.then(function(res){
console.log(res);
// 在这里第一次请求结束
// 发送第二次请求
var p1 = new Promise(function(resolve, reject){
ajax({
url: 'http://localhost:8888/test/second',
success: res => {
resolve(res)
},
error: res => {
reject('请求2错误')
}
})
})
return p1
}).then(function(res){
console.log(res);
// 测试请求2结束
// 发送第3个请求
var p2 = new Promise(function(resolve, reject){
ajax({
url: 'http://localhost:8887/test/third',
data: {
name: '张三',
age: 12
},
success: res => {
resolve(res)
},
error: res => {
reject('请求3错误')
}
})
})
return p2
}).then(res => {
console.log(res);
}).catch(res => {
console.log(res);
})
使用说明:
-
promise的then可以直接写在对象后面
-
如果在promise的then里面返回一个promise对象,那么里面的promise的then可以跟在外面的promise的then后面
补充:then和catch不会同时触发,也就是说,只要一个then出错了,执行最底下的catch就行,所以也就可以连续写多个then,一个catch就行。
promise成功解决了回调地狱的问题,但是这对于我们一个追求完美的攻城狮来说,远远不够,我们希望可以再次优化。
将ajax封装到promise中:将sendAjax中的success换成resolve,将sendAjax中的error换成reject
调用方式:
ajaxPromise({
url: 'http://localhost:8888/test/first',
dataType: 'text'
}).then(res => {
console.log(1, res);
// 第一个请求结束
return ajaxPromise({
url: 'http://localhost:8888/test/second',
})
}).then(res => {
console.log(2, res);
// 第2个结束
return ajaxPromise({
url: 'http://localhost:8888/test/third',
data: {
name: '张三',
age: 12
}
})
}).then(res => {
console.log(3, res);
})
ASYNC/AWAIT
es7提供了async/await来编写异步代码,是回调地狱的终极解决方案。
他可以将异步代码写的和同步代码一样。
语法:
async function fn() {
const res = await promise对象
}
只要是一个 promise 对象,那么我们就可以使用 async/await
来书写
例:
async function fn(){
var a = await ajaxPromise({
url: 'http://localhost:8888/test/first',
dataType: 'text'
})
// await可以将promise对象中调用resolve的时候传递参数等到出来
console.log(a);
}
fn()
使用说明:
-
async修饰的函数,需要调用的话,就正常调用
-
await必须包在async修饰的函数中
将async和封装的promise结合起来封装:
async function fn(){
// 发送第一个请求
var res1 = await ajaxPromise({
url: 'http://localhost:8888/test/first',
dataType: 'text'
})
console.log(1, res1);
// 发送第2个请求
var res2 = await ajaxPromise({
url: 'http://localhost:8888/test/second'
})
console.log(2, res2);
// 发送第3个请求
var res3 = await ajaxPromise({
url: 'http://localhost:8888/test/third',
data: {
name: '张三',
age: 12
}
})
console.log(3, res3);
}
fn()
跨域
正常情况下,我们使用ajax请求的数据都在自己的服务器上。但在一些特定的场景中,我们需要获取到别人的服务器上的数据,也就是在自己的服务器中的ajax要请求到别人的服务器的网址,这就是跨域。但是浏览器是不允许这样操作的,因为浏览器有同源策略。
同源策略:所谓同源,就是指域名、协议、端口都相同。比如说:在自己的localhost域名下请求www.baidu.com下的内容,这样的协议首先就不同,自己的是http,百度的是https,所以会被同源策略限制。
如何解决跨域?
1.借助当前服务器中的后端语言代替请求
自己的页面发送ajax -> 自己服务器中的某个页面 -> 让后端(nodejs/php/java)写爬虫 -> 请求数据 -> 数据回来以后,给到自己的ajax就好
2.借助当前服务器代替自己去请求 - proxy(服务器代理)
3.给目标地址设置允许跨域的响应头 - cors
4.利用某些标签在发送请求的时候,就不会受到同源策略的限制 - 动态创建标签案发起请求 - jsonp
jsonp的跨域原理:利用某些标签在发送请求的时候,不受同源策略的限制
例:
img引入别的网站的图片链接 - 会把请求回来的数据当做图片解析
link标签引入别的网站的样式文件 - 会把请求回来的数据当做css解析
iframe引入别的网站地址 - 会把请求回来的数据当做html文档解析
script标签引入别的网站的js文件 - 会把请求回来的数据当做js代码解析
jsonp利用script标签引入地址的时候,不受同源策略的限制,发送请求