JavaScript异步之从promise到await

1、从回调到Promise,为何要使用它

当咱们在执行异步任务的时候,每每会使用一个回调函数,它会在异步任务结束时被调用,它有一个共同点就是会在将来被执行。例如在node.js中异步读取文件:node

fs.readFile('/etc/passwd', (err, data) => {
  if (err) throw err;
  console.log(data);
});
复制代码

又如咱们写了个ajax请求函数:ajax

// 简化版,请忽略细节
const request = function(callback){
  let xhr = new XMLHttpRequest();
   // .....
  xhr.onreadystatechange = function(){
    callback(xhr)              
  }
}
//调用
request(function(xhr){
  if (xhr.readyState === 4 && xhr.status === 200) {
      // ....
  } else {
      //....
  }  
})

复制代码

甚至是一个定时器任务:promise

const doSomethingLater = function(delay, callback){
    setTimeout(callback, delay*1000)
}
// 调用
doSomethingLater(1, function(){
  // .....
})
复制代码

这样看使用回调的方式彷佛没什么问题,可是当回调函数中又有异步任务时,就可能出现多层回调,也就是回调地狱的问题。多层回调下降了代码的可读性和可维护性。bash

Promise为咱们作了什么

简单来说,Promise将异步任务包装成对象,将异步任务完成后要执行的函数传给then方法,经过resolve来调用该函数。如上面定时器任务能够改写成:异步

const doSomethingLater = function(delay){
    return new Promise((resolve)=>{
      setTimeout(()=>{ resolve() }, delay*1000)
    })
}
doSomethingLater(1)
    .then(()=>{
      console.log('任务1')
    })
复制代码

若是定时任务中又执行定时任务,就能够这样写,而不是再嵌套一层回调:async

doSomethingLater(1)
    .then(() => {
        console.log('任务1')
        return doSomethingLater(1)
    })
    .then(() => {
        console.log('任务2')
    })
复制代码

Promise的做用:函数

  • 把异步任务完成后的处理函数换个位置放:传给then方法,并支持链式调用,避免层层回调。
  • 捕获错误:无论是代码错误仍是手动reject(),均可以用一个函数来处理这些错误。

2、你可能不知道的Promise细节

用了Promise就是异步代码

就算你的代码原来没有异步操做:ui

Promise.resolve()
    .then(() => {
        console.log(1)
    })
console.log(2)
// 2
// 1
复制代码

这一点能够查一下事件循环相关知识spa

catch的另外一种写法

Promise.reject('error')
    .then(() => {
    })
    .catch((err) => {
        console.log(err)
    })
// 等价于
Promise.reject('error')
    .then(() => {
    })
    .then(null, (err) => {
        console.log(err)
    })
// 或者
Promise.reject('error')
    .then(() => {
    }, (err) => {
        console.log(err)
    })
复制代码

其实catch只是个语义化的语法糖,咱们也能够直接用then来处理错误。code

then 或 catch 方法始终返回promise对象

then方法第一个参数和第二个参数(或catch的参数),只是调用条件不一样。

Promise.resolve()
    .then(() => {
        return 1
    })
    .then((res) => {
        console.log(res) // 1
    })
    
Promise.resolve()
    .then(() => {
       // 不返回什么
    })
    .then((res) => {
        console.log(res) // undefined,由于函数默认返回undefined 
    })
   
复制代码

若是是返回一个promise对象:

Promise.resolve()
    .then(() => {
        return new Promise((resolve) => {
            resolve(2)
        })
    })
    .then((res) => {
        console.log(res) // 2, 根据返回的那个promise对象的状态来
    })
复制代码

咱们能够经过包装,使一个promise对象的最后状态始终是成功的: 例如:

const task = () => {
    return new Promise((resolve, reject) => {
        // ....
    })
}
task()
    .then((res) => {
        console.log(res)
    })
    .catch((err) => {
        console.log(err)
    })
复制代码

本来调用task函数时须要在外面加一层catch捕获错误,其实能够包装一下:

const task = () => {
    return new Promise((resolve, reject) => {
        // ....
    })
        .then((res) => {
            return {
                status: 'success',
                value: res
            }
        })
        .catch((err) => {
            return {
                status: 'fail',
                value: err
            }
        })
}
// 如今调用就会更简洁!
task()
    .then((result) => {
        console.log(result)
    })

复制代码

catch中报错也能够在后面继续捕获,由于catch也是返回promise对象嘛

Promise.reject('first error')
    .catch((err) => {
        console.log(err)
        throw new Error('second error')
    })
    .then(null, (err) => {
        console.log(err)
    })
复制代码

3、await:新的语法糖

await使得异步代码更像是同步代码,对串行的异步调用写起来更天然。await后面跟一个值或promise对象,若是是一个值,那么直接返回。若是是一个promise对象,则接受promise对象成功后的返回值。或者在await后面调用一个async函数

const task = async () => {
    return new Promise((resolve, reject) => {
        resolve(1)
    })
}
const handle = async () => {
    let value = await task()
    console.log(value)
}
handle() // 1
复制代码

使用await要注意错误的捕获,由于await只关心成功

const task = async () => {
    return new Promise((resolve, reject) => {
        reject(1)
    })
}
const handle = async () => {
    let value = await task()
    console.log(value)
}
handle()
复制代码

这样写会报错,因此要么在task函数中捕获错误,要么就在task调用时捕获,像这样:

const task = async () => {
    return new Promise((resolve, reject) => {
        reject(1)
    })
}
const handle = async () => {
    let value = await task().catch((err) => { console.log(err) })
    console.log(value) // undefine, 由于错误处理函数没返回值,你也能够返回错误信息,这样就会经过value拿到
}
复制代码

须要注意的是,使用await也会使代码变成异步的:

const handle = async () => {
    let value = await 1
    console.log(value)
}
handle()
console.log(2)
// 2
// 1
复制代码

完~

相关文章
相关标签/搜索