同步与异步的区别
同步
- 能直接拿到结果
- 比如去挂号,只要拿到号才回离开窗口,这一行代码才算执行结束
异步
- 不能直接拿到结果
- 比如在饭店排号就餐,拿到号可以离开去干别的事情
- 什么时候知道结果呢?
- 可以每10分钟去饭店问一下(轮询)
- 也可以扫码用微信接收通知(回调)
异步
以AJAX为例
- request.send()之后,并不直接得到response
- 必须等到readyState变为4后,浏览器回头调用request.onreadychange函数
- 才能得到request.response
getJSON.onclick=()=>{ const request=new XMLHttpRequest() request.open('GET','/5.json'); //readyState=1 //此时这个onreadychange函数是可以理解传递给了request,让request调用 request.onreadystatechange=()=>{ console.log(request.readyState) // 全部下载完成后readyState=4 if(request.readyState===4){ console.log("说明下载完成了") //状态码正常 if(request.status >= 200 && request.status<300){ const object=JSON.parse(request.response) }else{ alert("加载json失败"); } } }; // 发送请求 request.send() //readyState=2 //下载第一个字节信息后,readyState=3 //异步 //如果在发送请求后立马去拿结果,是拿不到的 console.log(request.response) //如果在具体的时间内再次尝试,会拿到结果,比如2s setTimeout(()=>{ console.log(request.response) },2000) };
回调callback举例
- 写给自己用的函数,不是回调
- 写给别人用的函数,就是回调
- request.onreadystatechacnge就是写给浏览器调用的函数
- 意思就是浏览器回调一下这个函数
- 写了却不调用,给别人调用的函数,就是回调
把函数1给另一个函数2
function f1(){} function f2(fn){ fn() } f2(f1) //f2()是我自己调用的
分析
- 我没有调用f1
- 我把f1传给另外一个函数f2
- 而这个函数f2又调用了我给它传的f1函数
- 满足上面三点的函数,就是回调函数,即f1
异步与回调的关系
关联
- 异步任务需在得到结果时通知js来拿结果
- 如何通知?
- 可以让js留一个函数地址给浏览器
- 异步任务完成后浏览器调用该函数地址(开始通知)
- 同时可以把异步任务的结果作为参数传给该函数(通知结果)
- 这个函数就是写给让浏览器调用的,所以是回调函数
区别
- 异步任务需要用到回调函数来拿到那个原本不能拿到的结果(也不一定非要用回调,可以用轮询)
- 回调函数不一定只用在异步任务里
- 回调函数也可以用到同步任务里
- array.forEach(n=>console.log(n)) 就是同步回调
- forEach接收了一个函数(),调用几次要看array有几个数据
异步函数
如果一个函数的返回值处于以下内部,那么这个函数就是异步函数
- setTimeout
- AJAX(即XMLHttpRequest)
- AddEventListener
- …
不要把AJAX设置成同步,这样会使请求期间的页面卡住,继而不能继续发送其他请求。
异步函数举例
此时拿不到异步函数的结果
function 摇号(){ setTimeout(()=>{ //箭头函数 //返回了真正的结果,所以这是一个异步函数 return parseInt(Math.random()*6)},1000) } //摇号函数没有返回值,因为本函数没有写return } const n=摇号() //此时n是undefined,因为没有返回值
用回调函数拿到异步函数的结果
- 首先写个函数,然后把函数地址x给它
function f1(x){ console.log(x) }
- 然后要求摇号函数得到结果后把结果作为参数传给f1
function 摇号(f1){ setTimeout(()=>{ //调用回调f1 f1(parseInt(Math.random()*6)) },1000) } function f1(x){ console.log(x) //此时拿到摇到的号了 } const n=摇号(f1)
- 异步任务不能拿到结果
- 传一个回调函数给异步任务
- 异步任务完成后调用回调
- 调用的时候把异步结果作为参数传给回调函数
进一步思考
如果异步任务有两个结果,比如成功或失败,该怎么办?
方1:回调接受两个参数
fs.readFile('./a.json',(error,data)=>{ //失败的参数,成功的参数 if(error){ console.log('失败');return } console.log(data.toString()) //成功 })
方2:写两个回调
//通过参数 //data是成功回调,error是失败回调 ajax('get','/a.json',(data)=>{},(error)=>{}) //通过对象 ajax('get','/a.json',{ //接受的是一个对象,成功是success,失败是fail success:()=>{},fail:()=>{} })
//封装ajax ajax = (method,url,options)=>{ const {success,fail}=options //析构赋值 //等同于 const success=options.success 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 ajax('get','/xxx',{ //左边是function的缩小,右边是箭头函数 success(response){},fail:(request,status)=>{} })
两个方法的缺点
- 不规范,名称随便命名,比如有人用success+error,有人用success+fail,有人用done+fail
- 容易出现回调地狱,代码变得不易看懂
- 很难进行处理错误
回调地狱
sayhello("first", function () { sayhello("second", function () { sayhello("third", function () { console.log("end"); ..... }); }); });
promise解决回调地狱(举例)
- 也是回调,但是不需要记success和fail
- then的第一个参数就是success
- then的第二个参数就是fail
熟记这个格式
return new Promise((resolve,reject)=>{})
//封装ajax ajax = (method,url,options)=>{ return new Promise((resolve,reject)=>{ const {success,fail}=options //析构赋值 //等同于 const success=options.success const request=new XMLHttpRequest() request.open(method,url) request.onreadystatechange=()=>{ if(request.readyState===4){ //promise状态变为成功就调用resolve,失败就调用reject if(request.status<400){ resolve.call(null,request.response) }else if(request.status>=400){ reject.call(null,request) } } } request.send() }) } // 新的调用方法 // 改成promise写法 //ajax()返回的是一个对象 //promise状态变为成功后then会调用第一个回调函数 ajax('get','/xxx').then((success)=>{},(err)=>{})
简单总结
让一个回调的异步函数变成promise的异步函数的步骤
- return new Promise((resolve,reject)=>{…})
- 任务成功就调用resolve(result)
- 任务失败就调用reject(error)
- resolve和reject会去调用成功和失败函数
- 使用的时候就直接 .then(success,fail)传入成功和失败函数
- then方法的返回结果是promise对象,对象状态由回调函数的执行结果决定。
- then方法的返回结果不是promise对象,状态为成功,返回值为对象的成功的值
- ….
上述封装的ajax有很多缺点,post无法上传数据,不能设置请求头…..这里只是学习一部分写的笔记