异步的概念
如果能直接拿到结果
那就是同步
比如你在医院挂号,拿到号才会离开窗口
同步任务可能消耗10毫秒,也可能需要3秒
总之不拿到结果是不会离开的
如果不能直接拿到结果
那就是异步
比如你在餐厅等位,拿到号后可以去逛街
想知道什么时候排到你?
你可以自己每十分钟去前台询问(轮询)
也可以扫码用微信接收通知(回调)
啥是回调
自己写的函数自己调用,不是回调
自己写的函数让别人调用,就是回调
function f1(){}
function f2(fn){
fn()
}
f2(f1)
以上代码中让f2调用f1,f1就是个回调函数
异步和回调的关系
关联
异步任务需要在得到结果时通知JS来拿结果
怎么通知?
可以让JS留一个函数地址(电话号码)给浏览器
异步任务完成时浏览器调用该函数地址即可(打电话通知)
同时把结果作为参数传给该参数
这个函数是我写给浏览器调用的,所以是回调函数
区别
异步任务需要用到回调函数来通知结果
但回调函数不一定只用在异步任务中
回调可以用在同步任务上
array.forEach(n=>console.log(n))
就是同步回调
分辨同步和异步
如果一个函数的返回值处于以下三个东西(当然不仅限于这三个):
- setTimeout
- AJAX(即XMLHttpRequest)
- AddEventListener
那么这个函数就是异步函数
举个例子
function 摇骰子(){
setTimeout(()=>{ // 箭头函数
return parseInt(Math.random() * 6) + 1
},1000)
//return undefined
}
摇骰子()
没有写return
,所以默认返回值是 undefined
箭头函数里有return
,返回真正的结果。所以这个箭头函数是一个异步函数
如果想要拿到结果
const n = 摇骰子()
console.log(n) // undefined
因为 摇骰子
的结果是 undefined
,所以 console.log
出来的也是 undefined
,并不能得到真正的结果。
这时候就可以使用回调写个函数f1
然后把函数地址给他,然后要求摇骰子函数得到结果后,把结果作为参数传给 f1
function f1(x){console.log(x)}
function 摇骰子(fn){
setTimeout(()=>{
fn(parseInt(Math.random() * 6) + 1)
},1000)
}
摇骰子(f1)
简化代码
摇骰子(x=>{
console.log(x)
})
还能简化
摇骰子(console.log)
总结
异步任务不能拿到结果
所以要传一个回调给异步任务
在异步任务完成时调用回调函数
调用的时候把结果作为参数
Promise
如果异步任务有两个结果(成功/失败)怎么办?
方法一:回调接受两个参数
fs.readFile('./1.txt',(error,data)=>{
if(error){consile.log('失败');return}
console.log(data.toString()) // 成功
})
方法二:搞两个回调
ajax('get','/1.json',data=>{},error=>{})
// 前面的是成功回调,后面的是失败回调
ajax('get','/1.json',{
success:()=>{},fail:()=>{}
})
// 接受一个对象,对象有两个 key 表示成功和失败
以上两种方法的不足:
- 不规范,命名五花八门,每个人的命名都可以不一样
success + error
、success + fail
、done + fail
等等等等 - 容易出现回调地狱,使代码变得看不懂
// 回调地狱举例
getUser(user=>{
getGroup(user,(groups)=>{
groups.forEach((g)=>{
g.filter(x=>x.ownerId===user.id)
.forEach(x=>console.log(x))
})
})
})
// 这只是四层回调
- 很难进行错误处理
为了解决这些问题,便有了Promise
以AJAX封装为例,首先是用回调的写法
ajax = (method, url, options)=>{
const {success, fail} = options //析构赋值
/*
*相当于
* const success = options.success
* const fail = options.fail
*/
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.response)
}
}
}
request.send()
}
ajax('GET', '/xxx', {
success(response){}, // function缩写,对象的函数一般都这样或者下面那样写。
fail: (request, status)=>{} // 箭头函数,上下两种意思都一样。
})
改用Promise写法,先改变调用方式
ajax('GET' ,'/xxx').then((response)=>{},(request)=>{})
// 虽然还是回调,但是去掉了了success和fail
// then 的第一个参数就是 success
// then 的第二个参数就是 fail
然后需要对ajax
的源码进行修改,从而获得含有.then()
的对象
ajax = (method, url, options)=>{
return new Promise((resolve, reject)=>{ //创建新的Promise对象,成功调用resolve,失败调用reject
const {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.send()
})
}
return new Promise((resolve,reject)=>{})
背下这五个单词
Promise小结
- 第一步
return new Promise((resolve,reject)=>{})
任务成功调用resolve(result)
任务失败调用reject(error)
resolve和reject会再去调用成功和失败函数 - 第二步
使用.then(success,fail)
传入成功和失败函数