异步、回调和Promise

28 阅读4分钟

异步的概念

如果能直接拿到结果

那就是同步
比如你在医院挂号,拿到号才会离开窗口
同步任务可能消耗10毫秒,也可能需要3秒
总之不拿到结果是不会离开的

如果不能直接拿到结果

那就是异步
比如你在餐厅等位,拿到号后可以去逛街
想知道什么时候排到你?
你可以自己每十分钟去前台询问(轮询
也可以扫码用微信接收通知(回调

啥是回调

自己写的函数自己调用,不是回调
自己写的函数让别人调用,就是回调

function f1(){}
function f2(fn){
    fn()
}
f2(f1)

以上代码中让f2调用f1,f1就是个回调函数

异步和回调的关系

关联

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

区别

异步任务需要用到回调函数来通知结果
但回调函数不一定只用在异步任务中
回调可以用在同步任务上
array.forEach(n=>console.log(n))就是同步回调

分辨同步和异步

如果一个函数的返回值处于以下三个东西(当然不仅限于这三个):

  • setTimeout
  • AJAX(即XMLHttpRequest)
  • AddEventListener

那么这个函数就是异步函数

举个例子

function 摇骰子(){
	setTimeout(()=>{  // 箭头函数
  	return parseInt(Math.random() * 6) + 1
  },1000)
  //return undefined
}

摇骰子()没有写return,所以默认返回值是 undefined
箭头函数里有return,返回真正的结果。所以这个箭头函数是一个异步函数

如果想要拿到结果

const n = 摇骰子()
console.log(n) // undefined

因为 摇骰子 的结果是 undefined ,所以 console.log 出来的也是 undefined ,并不能得到真正的结果。

这时候就可以使用回调写个函数f1然后把函数地址给他,然后要求摇骰子函数得到结果后,把结果作为参数传给 f1

function f1(x){console.log(x)}
function 摇骰子(fn){
  setTimeout(()=>{
    fn(parseInt(Math.random() * 6) + 1)
  },1000)
}
摇骰子(f1)

简化代码

摇骰子(x=>{
  console.log(x)
})

还能简化

摇骰子(console.log)

总结
异步任务不能拿到结果
所以要传一个回调给异步任务
在异步任务完成时调用回调函数
调用的时候把结果作为参数

Promise

如果异步任务有两个结果(成功/失败)怎么办?
方法一:回调接受两个参数

fs.readFile('./1.txt',(error,data)=>{
        if(error){consile.log('失败');return}
        console.log(data.toString()) // 成功
        })

方法二:搞两个回调

ajax('get','/1.json',data=>{},error=>{})
// 前面的是成功回调,后面的是失败回调
ajax('get','/1.json',{
    success:()=>{},fail:()=>{}
})
// 接受一个对象,对象有两个 key 表示成功和失败

以上两种方法的不足

  1. 不规范,命名五花八门,每个人的命名都可以不一样 success + errorsuccess + fail 、done + fail 等等等等
  2. 容易出现回调地狱,使代码变得看不懂
//  回调地狱举例
getUser(user=>{
  getGroup(user,(groups)=>{
    groups.forEach((g)=>{
      g.filter(x=>x.ownerId===user.id)
       .forEach(x=>console.log(x))
    })
  })
})
// 这只是四层回调
  1. 很难进行错误处理

为了解决这些问题,便有了Promise
以AJAX封装为例,首先是用回调的写法

ajax = (method, url, options)=>{
  const {success, fail} = options //析构赋值
  /* 
   *相当于
   * const success = options.success
   * const fail = options.fail 
   */
  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.response)
     }
   }
  }
  request.send()
}

ajax('GET', '/xxx', {
	success(response){}, // function缩写,对象的函数一般都这样或者下面那样写。
  fail: (request, status)=>{} // 箭头函数,上下两种意思都一样。
})

改用Promise写法,先改变调用方式

ajax('GET' ,'/xxx').then((response)=>{},(request)=>{})
// 虽然还是回调,但是去掉了了success和fail
// then 的第一个参数就是 success
// then 的第二个参数就是 fail

然后需要对ajax的源码进行修改,从而获得含有.then()的对象

ajax = (method, url, options)=>{
  return new Promise((resolve, reject)=>{ //创建新的Promise对象,成功调用resolve,失败调用reject
    const {success, fail} = options 
    const request = new XMLHttpRequest()
    request.open(method, url)
    request.onreadystatechange = ()=>{
     if(request.readyState === 4){
       if(request.status < 400){
         resolve.call(null, request.response)
       }else if(request.status >= 400){
         reject.call(null, request)
       }
     }
    }
    request.send()
	})
}

return new Promise((resolve,reject)=>{})背下这五个单词

Promise小结

  • 第一步
    return new Promise((resolve,reject)=>{})
    任务成功调用resolve(result)
    任务失败调用reject(error)
    resolve和reject会再去调用成功和失败函数
  • 第二步
    使用.then(success,fail)传入成功和失败函数