一、异步和同步
- 同步:如果能直接拿到结果,则为同步
eg:在医院挂号,要拿到号才能离开窗口,不拿到结果不会离开
- 异步:不能直接拿到结果
eg:在餐厅门口等位,拿到号可以去逛街,每10分钟去餐厅问一下(轮询),也可以扫码微信接受通知(回调)
eg:以AJAX为例,request.send()之后,并不能直接得到response,必须等到readyState变为4,浏览器回头调用request.onreadystatechange函数,才能得到request.response
二、回调
回调:写给自己用的函数,并不是回调,写给别人调用的函数,才叫回调
eg:request.onreadystatechange就是写给浏览器调用的
-
function f1() {} function f2(fn){ fn() } f2(f1)- 没有调用f1
- 把f1传给f2
- f2调用了f1
- f1是回调
三、异步和回调
3.1关联
- 异步任务需要在得到结果的时候通知JS来拿结果
- 可以让JS写一个函数地址(电话号码)给浏览器
- 异步任务完成时浏览器调用该函数地址(拨打电话)
- 同时把结果作为参数传给该函数(电话里说可以回来吃了)
- 这个函数是写给浏览器调用的,所以是回调函数
3.2区别
-
异步任务需要用到回调函数来通知结果,但也可以用轮询
-
回调函数不一定只用在异步任务,也可以用在同步任务
eg:array.forEach(n => console.log(n)) 就是同步回调
四、异步函数
如果一个函数的返回值处于下列三个API里面,则这个函数为异步函数(还有其他API之后再说明)
- setTimeout
- AJAX(即XMLHttpRequest)
- AddEventListener
AJAX一般不设置为同步,这样做会使得请求期间页面卡住
4.1异步函数举例
-
function 摇骰子(){ setTimeout(()=>{//箭头函数 return parseInt((Math.random()*6)+1 },1000) }- 摇骰子() 没有写return,那么return undefined
- 箭头函数里面有return,返回真正结果
- 这是一个异步函数/异步任务
怎么拿到异步结果?
- 可以写一个函数,把函数地址给摇骰子()
function f1(x){console.log(x)}
摇骰子(f1)
- 然后要求摇骰子函数得到结果后把结果作为参数传给f1
function f1(x){console.log(x)}
摇骰子(f1)
function 摇骰子(fn){
setTimeout(()=>{//箭头函数
fn(parseInt((Math.random()*6)+1)
},1000)
}
- 因为f1声明之后只用了一次,所以可以删掉f1
function f1(x){console.log(x)}
摇骰子(f1)
//改为
摇骰子(x => {
console.log(x)
})
//再简化为,但是如果参数个数不一致,不能这样简化
摇骰子(console.log)
面试题:
const array = ['1','2','3'].map(parseInt)
console.log(array)
// [1,NaN,NaN]
//上述写法相当于下面
const array = ['1','2','3'].map((item,i,arr)=>{ //map会传3个参数给parseInt
return parseInt(item,i,arr)
//parseInt("1",0,arr) => 1
//parseInt("2",1,arr) => NaN 相当于把2作为一进制的数进行解析
//parseInt("3",2,arr) => NaN 相当于把3作为二进制的数进行解析
})
console.log(array)
//正常来说要写成箭头函数
const array = ['1','2','3'].map(item => parseInt(item))
console.log(array)
4.2异步任务不同结果处理
4.2.1两种处理方式
- 回调接受两个参数
fs.readFile('./1.txt',(error,data)=>{
if(error){console.log('失败');return}
console.log(data.toString()) //成功
})
- 两个回调
ajax('get','1.json',data=>{},error=>{})
//前面函数是成功的回调,后面函数是失败的回调
4.2.2不足之处
- 不规范,命名五花八门,有人用success+error,有人用success+fail,还有用done+fail
- 容易出现回调地狱,代码难以理解
getUser( user => {
getGroups(user, (groups)=>{
groups.forEach( (g)=>{
g.filter(x => x.ownerId === user.id)
.forEach(x => console.log(x))
})
})
})
//上述只是四层回调
- 很难进行错误处理
4.3Promise
以AJAX封装为例
ajax = (method, url, options)=>{
const {success, fail} = options //析构赋值
const request = new XMLHttpRequest()
request.open(method, url)
request.onreadystatechange = ()=>{
if(request.readyState === 4){
// 成功调用success 失败调用fail
if(request.status < 400){
success.call(null, request.response)
}else if(request.status >= 400){
fail.call(null, request, request.status)
}
}
}
request.send()
}
ajax('get','/xxx',{
success(response){},fail:(request,status)=>{} //左边是function的缩写,右边是箭头函数
})
改成Promise写法,先改调用函数
ajax('get','/xxx')
.then((response){},(request)=>{})
//then的第一个参数是success,第二个参数是fail
//ajax()返回一个含有.then()方法的对象
接下来改造ajax的源代码
ajax = (method, url, options)=>{
return new Promise((resolve,reject)=>{
const {success, fail} = options //析构赋值
const request = new XMLHttpRequest()
request.open(method, url)
request.onreadystatechange = ()=>{
if(request.readyState === 4){
// 成功调用resolve 失败调用reject
if(request.status < 400){
resolve.call(null, request.response)
}else if(request.status >= 400){
reject.call(null, request)
}
}
}
request.send()
})
}
封装的AJAX的缺点
- post无法上传数据
- 不能设置请求头
如何解决:
- 使用jQuery.ajax
- 使用axios