1

一、从回调到Promise,为什么要使用它

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

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

又如我们写了个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 {
      //....
  }  
})

甚至是一个定时器任务:

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

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

Promise为我们做了什么

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

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

如果定时任务中又执行定时任务,就可以这样写,而不是再嵌套一层回调:

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

Promise的作用:

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

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

用了Promise就是异步代码

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

Promise.resolve()
    .then(() => {
        console.log(1)
    })
console.log(2)
// 2
// 1

这一点可以查一下事件循环相关知识

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来处理错误。

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)
    })

三、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

完~


toln
49 声望1 粉丝

前端开发@深圳