异步与回调

519 阅读4分钟

一、异步和同步

  • 同步:如果能直接拿到结果,则为同步

eg:在医院挂号,要拿到号才能离开窗口,不拿到结果不会离开

  • 异步:不能直接拿到结果

eg:在餐厅门口等位,拿到号可以去逛街,每10分钟去餐厅问一下(轮询),也可以扫码微信接受通知(回调)

eg:以AJAX为例,request.send()之后,并不能直接得到response,必须等到readyState变为4,浏览器回头调用request.onreadystatechange函数,才能得到request.response

二、回调

回调:写给自己用的函数,并不是回调,写给别人调用的函数,才叫回调

eg:request.onreadystatechange就是写给浏览器调用的

  • function f1() {}
    function f2(fn){
        fn()
    }
    f2(f1)
    
    • 没有调用f1
    • 把f1传给f2
    • f2调用了f1
    • f1是回调

三、异步和回调

3.1关联

  • 异步任务需要在得到结果的时候通知JS来拿结果
  • 可以让JS写一个函数地址(电话号码)给浏览器
  • 异步任务完成时浏览器调用该函数地址(拨打电话)
  • 同时把结果作为参数传给该函数(电话里说可以回来吃了)
  • 这个函数是写给浏览器调用的,所以是回调函数

3.2区别

  • 异步任务需要用到回调函数来通知结果,但也可以用轮询

  • 回调函数不一定只用在异步任务,也可以用在同步任务

    eg:array.forEach(n => console.log(n)) 就是同步回调

四、异步函数

如果一个函数的返回值处于下列三个API里面,则这个函数为异步函数(还有其他API之后再说明)

  • setTimeout
  • AJAX(即XMLHttpRequest)
  • AddEventListener

AJAX一般不设置为同步,这样做会使得请求期间页面卡住

4.1异步函数举例

  • function 摇骰子(){
        setTimeout(()=>{//箭头函数
            return parseInt((Math.random()*6)+1
                            },1000)
    }
    
    • 摇骰子() 没有写return,那么return undefined
    • 箭头函数里面有return,返回真正结果
    • 这是一个异步函数/异步任务

怎么拿到异步结果?

  • 可以写一个函数,把函数地址给摇骰子()
 function f1(x){console.log(x)}
 摇骰子(f1)
  • 然后要求摇骰子函数得到结果后把结果作为参数传给f1
 function f1(x){console.log(x)}
 摇骰子(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)

面试题:

const array = ['1','2','3'].map(parseInt)
console.log(array)
// [1,NaN,NaN]

//上述写法相当于下面
const array = ['1','2','3'].map((item,i,arr)=>{ //map会传3个参数给parseInt
    return parseInt(item,i,arr)
    //parseInt("1",0,arr) => 1
    //parseInt("2",1,arr) => NaN 相当于把2作为一进制的数进行解析
    //parseInt("3",2,arr) => NaN 相当于把3作为二进制的数进行解析
    
})
console.log(array)

//正常来说要写成箭头函数
const array = ['1','2','3'].map(item => parseInt(item))
console.log(array)

4.2异步任务不同结果处理

4.2.1两种处理方式

  • 回调接受两个参数
fs.readFile('./1.txt',(error,data)=>{
 if(error){console.log('失败');return}
  console.log(data.toString()) //成功
})
  • 两个回调
ajax('get','1.json',data=>{},error=>{})
//前面函数是成功的回调,后面函数是失败的回调

4.2.2不足之处

  • 不规范,命名五花八门,有人用success+error,有人用success+fail,还有用done+fail
  • 容易出现回调地狱,代码难以理解
getUser( user => {
    getGroups(user, (groups)=>{
        groups.forEach( (g)=>{
           g.filter(x => x.ownerId === user.id)
              .forEach(x => console.log(x))
                })
        })
})
//上述只是四层回调
  • 很难进行错误处理

4.3Promise

以AJAX封装为例

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')
    .then((response){},(request)=>{})
//then的第一个参数是success,第二个参数是fail
//ajax()返回一个含有.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()
   })   
}

封装的AJAX的缺点

  • post无法上传数据
  • 不能设置请求头

如何解决:

  • 使用jQuery.ajax
  • 使用axios