异步与回调

3周前 (10-01) 117次浏览 已收录 0个评论

同步与异步的区别

同步

  • 能直接拿到结果
  • 比如去挂号,只要拿到号才回离开窗口,这一行代码才算执行结束

异步

  • 不能直接拿到结果
  • 比如在饭店排号就餐,拿到号可以离开去干别的事情
  • 什么时候知道结果呢?
    • 可以每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来拿结果
  • 如何通知?
    1. 可以让js留一个函数地址给浏览器
    2. 异步任务完成后浏览器调用该函数地址(开始通知)
    3. 同时可以把异步任务的结果作为参数传给该函数(通知结果)
  • 这个函数就是写给让浏览器调用的,所以是回调函数

区别

  • 异步任务需要用到回调函数来拿到那个原本不能拿到的结果(也不一定非要用回调,可以用轮询)
  • 回调函数不一定只用在异步任务里
  • 回调函数也可以用到同步任务里
    1. array.forEach(n=>console.log(n)) 就是同步回调
    2. 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. 异步任务不能拿到结果
  2. 传一个回调函数给异步任务
  3. 异步任务完成后调用回调
  4. 调用的时候把异步结果作为参数传给回调函数

进一步思考

如果异步任务有两个结果,比如成功或失败,该怎么办?

方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无法上传数据,不能设置请求头…..这里只是学习一部分写的笔记

渣渣龙, 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:异步与回调
喜欢 (1)

您必须 登录 才能发表评论!