术语
同步
异步
- 如果不能直接拿到结果就是异步
- 每隔一段时间询问数据,叫做轮询
- 直接调用,叫做回调
举例
- 以AJAX请求JSON文件为例
- 当在本地请求JSOn文件,得到响应数据(response),大概需要几毫秒

- 在发送请求后立即打印出响应数据,不能直接得到数据


- 需要在这几毫秒之后才可以
- 调用setTiemout(),2秒之后打印,得到响应数据


- 当console.log放入onreadyStateChange函数里面时
- 直接得到响应数据
- 因为此时,必须等到oneadyState变为4(即处于完成响应阶段)时,才能得到响应数据
- 由此证明浏览器回头调用这个函数,得到了响应数据,此过程叫做回调


回调 callback
- 写给自己的函数,不是回调
- 写给别人用的函数,就是回调
- request.onreadystteChange就是写给浏览器调用的
- 即浏览器回头调用一下这个函数
- 这里的“回头”指将来的某一时刻
回调举例
例:把f1给函数f2
function f1(){ }
function f2(fn){
fn()
}
f2(f1)
分析
- f1没有调用- 因为直接调用的是f2
- f1传给了f2
- f2调用了f1
- 那么f1就是写给f2的调用的函数,所以f1是调用函数
异步和回调的关系
- 关联
- 异步任务需要在得到结果时通知JS来拿结果
- 方法:让JS留一个函数地址给浏览器
- 异步任务完成时,浏览器调用改函数地址即可
- 同时把结果作为参数传给该函数
- 这个函数是给浏览器调用,拿到结果的所以是回调函数
- 区别
- 异步任务需要用到回调函数来通知结果
- 但回调函数不一定只用你在异步任务里
- 回调可以用到同步任务里
- aeeray.forEach(n=>console.log(n))
判断函数是同步还是异步
- 如果一个函数的返回值处于
- setTimeout
- AJAX(即XMLHttpRequest)
- AddEventListener
- 这三个常用API内部,那么函数就是异步函数
- 非常不合理的做法:AJAX设置成同步
- 正常情况下,异步操作,先请求JSON,再请求XML
- 浏览器过程非常的流畅

- 而当强行把它变成同步(传入的参数改为false)
- 必须先拿到JSON请求得到的数据之后,才能得到XNM的数据。在等待JSON的过程中,XML请求按钮虽然按下,但是XML数据根本拿不到,在实际开发中,会造成网页卡顿

摇骰子案例
例1:
function 摇骰子(){
setTimeOut(()=>{
return parseInt(Math.random()*6)+1
},1000)
}
- 摇骰子里面没有写return,那就是treturn undefined
- 箭头函数里面没有return,返回真正的结果
- 所以这是一个异步函数/任务
- 摇骰子函数里面的return和箭头函数里面的return属于不同的函数
改动
const n=摇骰子()
console.log(n)
const f1(x){console.log(x)}
摇骰子(f1)
- 然后要求摇骰子函数得到结果后把结果作为参数传给f1
function 摇骰子(fn){
setTimeout(()=>{
fn(parseInt(Math.random()*6)+1)
},1000)
}
简化代码
- 简化为箭头函数
- 由于f1声明之后只用了一次,所以可以删掉f1
function f1(x){ console.log(x)}
摇骰子(f1)
摇骰子(x=>{
console.log(x)
})
- 再次简化
- x作为中间的媒介,将console.log传到摇骰子()函数,那么直接将console.log作为参数,由摇骰子()函数来调用它
摇骰子(console.log)
- 小结:以上简化只能在传进来的参数一致的情况下才可以否则就会产生错误(见下方例题)
案例
- 错误简化
- 想要得到结果[1,2,3]
- 但是map函数需要传入三个参数,产生了错误

- map函数完整展开
- map函数接收三个参数:对应的元素、对应第几个、以及对应的数组
- 里面的parseInt默认接收对应的元素
- 所以结果正确
- 而简化之后,接收的参数不一致

- 错误代码思路展示(注:以下代码错误)
- 如果按照错误简写
- 按照这个逻辑map和parseInt接收相同的参数
- parseInt原本接收两个参数,分别为当前元素,和转成多少进制数
- 此时parseInt接收了三个参数,当接收1时,第二个参数是0(i=0),但是没有0进制,所以相当于没传参数,数组也传不进去
- 当传入2,第二个参数为1,表示1进制,直接报错
- 传入3同理

- 正确简写

异步小结
- 异步任务不能拿到结果
- 于是我们传一个回调任务给异步任务
- 异步任务完成时调用回调
- 调用的时候把结果作为参数
异步任务有两个结果 成功或者失败,解决方案
fs.readFile('./1.txt',(error.data)=>{
if(error){console.log('失败了');return}
console.log(data.toString())
})
ajax('get','/1.json',data=>{},error=>{})
- 另一种方式
ajax('get','1.json',{
success:()=>{},fail:()=>{}
} )
以上方法的不足之处
- 不规范,名称五花八门(方法一)
- 容易出现回调地狱(比如回调20层),代码变得看不懂(方法二)
getUser(user=>{
getGroups(user,(groups)=>{
groups.forEach((g)=>{
g.filter(x=>x.ownerId===user.id)
.forEach(x=>console.log(x))
})
})
})
解决思路
- 规范回调的名字或者顺序
- 拒绝回调地狱,让代码可读性更强
- 如何很方便地捕获错误
promise设计模式
以AJAX的封装为例
ajx=(method,url,options)=>{
cosnt {success,fail}=options
const request =new XMLHttpRequest()
request.open(method,url)
request.onreadystatechange=()=>{
if (request.readyState===4){
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)=>{}
}
- 先改变调用方式
- 上面调用了两个回调,还使用了success和fail
- 改成promise写法
ajax('get','/xxx')
.then((response)=>{} ,(request)=>{} )
- 虽然也是回调
- 但是不需要记success和fai了
- then的第一参数就是success
- then的第二个参数就是fail
- ajax返回了一个含有.then方法的对象
- 如果要得到这个含有.then()的对象,只能改造ajax的源码
- 以下代码思路:ajax直接return,接收两个参数resolve和reject函数,这两个函数可以调用then后面的函数
ajax=(method,url,options)=>{
return new Promise((resolve,reject)=>{
cosnt {success,fail}=options
const request =new XMLHttpRequest()
request.open(method,url)
request.onreadystatechange=()=>{
if (request.readyState===4){
if (request.status<400){
resolve.call(null,request.response)
}else if(request.status>=400){
reject.call(null,request,request.status)
}
}
}
request.send()
})
}
重要结构式
- return new Promise((resolve,reject)=>{})
小结:如何让一个回调的异步函数变成Promis的异步函数
- 第一步
return new Promise((resolve,reject)+>{....})
- 任务成功调用resolve(result)
- 任务失败调用reject(error)
- resolve和reject会再去调用成功和失败函数
- 第二步
- 使用.then(success,fail)传入成功和失败函数
- 补充:promise不可被取消
总结
- 异步是什么?
- 异步为什么会用到回调?
- 回调存在的问题有哪些?
- 回调地狱(多层回调),代码复杂、难以理解
- 名称存在歧义
- 无法进行错误处理
- Promise是什么?
- Promise 基本的机构
return new Promise((resolve,reject)=>{})
- Promise是前端解决异步问题的统一方案