异步与promise

352 阅读5分钟

什么是异步?什么是同步

异步

如果能直接拿到结果,就是同步;

举例:在医院现场挂号,拿到号才会离开窗口。

如果不能直接拿到结果,就是异步。

举例:在餐厅门口等位,你拿到号后可以去逛街

什么时候才能真正吃饭呢?

你可以每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))就是同步回调

判断同步异步

如果一个函数的返回值处于

  • setTimeout
  • AJAX(即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 表示成功和失败
回调不一定要用函数的形式,还可以用对象的形式
  • 这些方法的不足 不管方法一还是方法二,都有问题
  1. 不规范,名称五花八门,有人用success+error,有人用success+fail,有人用done+fail
  2. 容易出现回调地狱,代码变得看不懂
  3. 很难进行错误处理

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的第一个参数就是successthen的第二个参数就是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的异步函数?

  1. 第一步
  • return new promise ((resolve, reject)=>{...})
  • 任务成功则调用resolve(result)
  • 任务失败则调用reject(error)
  • resolvereject会再去调用成功和失败函数 resolvereject并不是.then(success, fail)里面的 successfail,resolve调用success,reject调用fail。
  1. 第二步
  • 使用.then(success, fail)传入成功和失败函数 promise更高级的用法,后续再更

以上封装的AJAX的缺点

  • post无法上传数据 request.send(这里可以上传数据)
  • 不能设置请求头 request.setRequestHeader(key, value)
  • 怎么解决 使用jQuery.ajax (虽然jQuery.ajax非常完美强大,但是已经过时了,更多在用axios)

使用axios(可参考方应杭博客

拓展知识点:

promise是不可以被取消的,但是axios可以取消,因为axios发明了cancelToken