什么是异步?什么是同步
异步
如果能直接拿到结果,就是同步;
举例:在医院现场挂号,拿到号才会离开窗口。
如果不能直接拿到结果,就是异步。
举例:在餐厅门口等位,你拿到号后可以去逛街
什么时候才能真正吃饭呢?
你可以每10分钟去餐厅问一下(轮询)
也可以扫码用微信接收通知(回调)
回调 callback
写了却不调用,给别人调用的函数,就是回调。
举例:
把函数1给另一个函数2
function f1(){}
function f2(fn){
fn()
}
f2(f1)
分析:
- 我调用了
f1没有?答:没有调用 - 我把
f1传给f2(别人)了没有?答:传了 f2调用了f1没有?答:f2调用了f1- 那么,
f1就是我写给f2调用的函数,所以,f1是回调。
异步和回调的关系
关联
- 异步任务需要在得到结果时通知JS来拿结果
- 如何通知呢?
- 可以让JS留一个函数地址给浏览器
- 异步任务完成时浏览器调用该函数地址即可
- 同时把结果作为参数传给该函数
- 这个函数是我写给浏览器调用的,所以是回调函数
区别
- 异步任务需要用到回调函数来通知结果,也可以用轮询
- 但回调函数不一定只用在异步任务里
- 回调可以用到同步任务里
arry.forEach(n => console.log(n))就是同步回调
判断同步异步
如果一个函数的返回值处于
setTimeoutAJAX(即XMLHttpRequest)AddEventListener- 如果返回值处于这三个东西的内部,那么这个函数就是异步函数
AJAX可以设置为同步的,但是这样做会使请求期间页面卡住,不要用。
举例1
function 摇骰子(){
setTimeout(()=>{
return parseInt(Math.random() * 6)+1},1000)
// return undefined
}
分析
摇骰子()没有写return,那就是return undefined- 箭头函数里有
return,返回真正的结果 - 所以这是一个异步函数/异步任务
举例1续
const n = 摇骰子()
console.log(n) //undefined
如何拿到异步结果
- 答:可以用回调。写个函数,然后把函数地址给它
function 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)
})
再简化为
摇骰子(console.log)
// 如果参数个数不一致就不能这样简化
小结
- 异步任务不能拿到结果
- 于是我们传一个回调给异步任务
- 异步任务完成时调用回调
- 调用的时候把结果作为参数
如果异步任务有两个结果,成功或失败,怎么办
两个结果
- 方法一:回调接受两个参数
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: ()=>{}
})
//接受一个对象,对象有两个 key 表示成功和失败
回调不一定要用函数的形式,还可以用对象的形式
- 这些方法的不足 不管方法一还是方法二,都有问题
- 不规范,名称五花八门,有人用success+error,有人用success+fail,有人用done+fail
- 容易出现回调地狱,代码变得看不懂
- 很难进行错误处理
Promise
promise是前端解决异步问题的统一方案
以AJAX的封装为例,解释Promise的用法
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', {
success(response){}, fail: (request, status)=>{}
})
//上面用到了两个回调,还使用了 success和fail
//改成promise写法
ajax('get', '/xxx')
.then((response)=>{}, (request)=>{}) //promise规定了成功回调和失败回调都只能接受一个参数
虽然也是回调,但是不需要记success和fail了;
then的第一个参数就是success,then的第二个参数就是fail;
ajax()返回了一个含有.then()方法的对象,
那么如何得到这个含有.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()
})
}
小结
如何把一个回调的异步函数变成promise的异步函数?
- 第一步
return new promise ((resolve, reject)=>{...})- 任务成功则调用
resolve(result) - 任务失败则调用
reject(error) resolve和reject会再去调用成功和失败函数resolve和reject并不是.then(success, fail)里面的success和fail,resolve调用success,reject调用fail。
- 第二步
- 使用
.then(success, fail)传入成功和失败函数 promise更高级的用法,后续再更
以上封装的AJAX的缺点
- post无法上传数据
request.send(这里可以上传数据) - 不能设置请求头
request.setRequestHeader(key, value) - 怎么解决 使用jQuery.ajax (虽然jQuery.ajax非常完美强大,但是已经过时了,更多在用axios)
使用axios(可参考方应杭博客)
拓展知识点:
promise是不可以被取消的,但是axios可以取消,因为axios发明了cancelToken。