Promise/async/await学习总结 | 青训营笔记

117 阅读8分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天

一、早期代码困境

  • 由于Js是单线程的,耗时操作都是交给浏览器处理(浏览器维护一个队列)
  • 异步任务不会占用Js线程,因为其队列是浏览器维护的,当一个异步任务执行完毕以后,会通知js主线程,做处理

早期的做法是通过 回调函数 处理。

但是回调函数存在弊端:

  • 成功/失败等函数传递太灵活了,没有规范
  • 传参顺序容易错误(需要查看框架源码,以查找传参顺序)
function requestData(url, successCB, failureCB) {
  setTimeout(() => {
    if (url === 'iceweb.io') {
      successCB('我成功了,把获取到的数据传出去', [{name:'ice', age:22}])
    } else {
      failureCB('url错误,请求失败')
    }
  }, 3000)
}

//3s后 回调successCB 
//我成功了,把获取到的数据传出去 [ { name: 'ice', age: 22 } ]
requestData('iceweb.io', (res, data) => console.log(res, data), rej => console.log(rej))

//3s后回调failureCB
//url错误,请求失败
requestData('icexxx.io', res => console.log(res) ,rej => console.log(rej))

二、Promise

1. executor

new Promise 时,传入的回调函数(resolve,reject)⇒{}就是executor执行器,这个函数是是立即执行的。

new Promise((resolve, reject) => {
  console.log(`executor 立即执行`)
})

调用resolve或者reject触发then传入的回调函数。

调用reject触发catch传入的回调函数。

2. 使用Promise重构requestData

function requestData(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url === 'iceweb.io') {
        //只能传递一个参数
        resolve('我成功了,把获取到的数据传出去')
      } else {
        reject('url错误,请求失败')
      }
    }, 3000)    
  })
}

//1. 请求成功
requestData('iceweb.io').then(res => {
  //我成功了,把获取到的数据传出去
  console.log(res)
})

//2. 请求失败

//2.2 第一种写法
//url错误,请求失败
requestData('iceweb.org').then(res => {},rej => console.log(rej))

//2.2 第二种写法
//url错误,请求失败
requestData('iceweb.org').catch(e => console.log(e))

在使用上,正确的时候调用resolve方法,失败调用reject方法。

在异常处理中:

then方法可以传入两个回调 (fulfilled, rejected)=> {}

  1. fulfilled (resolve后执行)
  2. rejected(reject后执行,传入这个就可以不调用catch了)

引入Promise的好处:

  • 统一规范,增强阅读性和扩展性
  • 小幅度减少回调地狱

3. Promise的状态

一个承诺,其有三个状态,分别为:待定、已兑现、已拒绝。

在Promise中对应:

  • pending:待定,执行了executor还在等待结果
  • fulfilled:已兑现,执行了resolve会改变状态为fulfilled
  • rejected:已拒绝,执行了reject会改变状态为rejected

状态一旦发生改变就不可逆转

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('失败')
    resolve('成功')
  }, 3000);
})

promise.then(res => console.log(res)).catch(err => console.log(err))

//失败

调用reject后,状态已经发生改变,不可逆,再调用resolve是无效的

4. Promise中调用resolve传入不同类型的值的区别

  1. **传入普通值或者对象:**这个值会作为then的回调参数
  2. **传入另一个Promise:**新Promise会决定原Promise的状态
  3. **传入有thenable对象:**会执行then方法,并传入resolve和reject,此时的状态取决于then方法内调用了resolve还是reject,这称为thenable模式。

thenable对象:含有then方法的对象then(resolve, reject)

下方个示例分别对应上方三种情况:

const promise = new Promise((resolve, reject) => {
  resolve({name: 'ice', age: 22})
})

promise.then(res => console.log(res))

// {name: 'ice', age: 22}
const promise = new Promise((resolve, reject) => {
  resolve(new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('ice')
    }, 3000);
  }))
})

promise.then(res => console.log(res))

//3s后 ice
const promise = new Promise((resolve, reject) => {
  resolve({
    then(res, rej) {
      res('hi ice')
    }
  })
})

promise.then(res => console.log(res))

// hi ice

5. Promise的实例方法

即存放在Promise.prototype中的方法

(1)then

  • then(resolve, reject)
  • 接收两个参数,第一个是成功的回调,第二个是失败的回调
  • 如果只捕获错误,可以在第一个参数设置为null或者””占位
const promise = new Promise((resolve, reject) => {
  // resolve('request success')
  reject('request error')
})

promise.then(null, rej => console.log(rej))

//request error
  • then多次调用:会重复传入then的回调函数,但是Promise内部的executor不会重复执行,会直接将结果传给回调函数(Promise的状态发生改变不可逆)

image.png

image.png

  • 方法返回值:返回一个Promise,这个返回的Promise的状态如何决定?

    • 返回普通值(不返回值则视为返回undefined):相当于主动调用Promise.resolve并法返回值传递到then中,状态为fulfilled
    const promise = new Promise((resolve, reject) => {
      resolve('hi ice')
    })
    
    promise.then(res => ({name:'ice', age:22}))
           .then(res => console.log(res))
           
    //{name:'ice', age:22}
    
    • 主动返回一个Promise,那么这个返回对象的状态和这个返回的Promise和其内部调用的是resolve还是reject有关。
    const promise = new Promise((resolve, reject) => {
      resolve('hi ice')
    })
    
    promise.then(res => {
      return new Promise((resolve, reject) => {
        resolve('then 的返回值')
      })
    }).then(res => console.log(res))
    
    //then 的返回值 
    // 状态为fulfilled
    
    • 返回一个thenable对象:状态取决于是thenable对象内调用了resolve还是reject
    promise.then(res => {
      return new Promise((resolve, reject) => {
        resolve('then 的返回值')
      })
    })
    

(2)catch

  • catch(err_callback)
  • 多次调用和then同理
  • 返回值和then同理

(3)finally方法

ES9(2018)新实例方法

不论promise最后的状态是fulfilled还是reject都会执行

const promise = new Promise((resolve, reject) => {
  resolve('hi ice')
})

promise.then(res => console.log(res)).finally(() => console.log('finally execute'))

//finally execute

6. Promise的类方法/静态方法

image.png

(1)resolve:状态为fulfilled,已预知结果可以这样写

Promise.resolve('ice')
//等价于
new Promise((resolve, reject) => resolve('ice'))

(2)reject:状态为rejected,已预知结果可以这样写

Promise.reject('ice error')
//等价于
new Promise((resolve, reject) => reject('ice error'))

(3)all:传入一个可迭代对象,返回一个promise

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('hi ice')
  }, 1000);
})

const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('hi panda')
  }, 2000);
})

const promise3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('hi grizzly')
  }, 3000);
})

Promise.all([promise1, promise2, promise3]).then(res => console.log(res))

//[ 'hi ice', 'hi panda', 'hi grizzly' ]
  • 当可迭代对象里面所有promise都resolve的时候,才会调用then(返回的Promise才为fulfilled),且then传入的是各个promise的结果数组
const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('hi ice')
  }, 1000);
})

const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('hi panda')
  }, 2000);
})

const promise3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('hi grizzly')
  }, 3000);
})

Promise.all([promise1, promise2, promise3]).then(res => console.log(res))

//[ 'hi ice', 'hi panda', 'hi grizzly' ]
  • 只要有一个promise为rejected,那么会调用catch方法(rejected),catch传入第一个reject的结果
const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('hi ice')
  }, 1000);
})

const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('hi panda')
  }, 2000);
})

const promise3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('hi grizzly')
  }, 3000);
})

Promise.all([promise1, promise2, promise3]).then(res => console.log(res)).catch(err => console.log(err))

//hi panda

(3)allSettled

对于all方法,当存在reject时,只会在catch传入第一个reject的数据,后面其他的promise的结果会丢失。

ES11新增语法 Promise.allSettled ,无论结果是fulfilled还是rejected,都会把结果传递出来。

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('hi ice')
  }, 1000);
})

const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('hi panda')
  }, 2000);
})

const promise3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('hi grizzly')
  }, 3000);
})

Promise.allSettled([promise1, promise2, promise3]).then(res => console.log(res))

/* [
  { status: 'rejected', reason: 'hi ice' },
  { status: 'fulfilled', value: 'hi panda' },
  { status: 'rejected', reason: 'hi grizzly' }
] */
  • 该方法需要所有Promise都有结果(fulfilled或rejected)以后,才会有结果。
  • 其中一个没有结果,那么allSettled方法始终得不到结果。

(4)race

竞赛,将传入的promise列表中,第一个获得的结果(不管是fulfilled还是rejected)作为自己的结果。

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('hi error')
  }, 1000);
})

const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('hi panda')
  }, 2000);
})

Promise.race([promise1, promise2])
       .then(res => console.log(res))
       .catch(e => console.log(e))
       
//hi error

(5)any

any success,或第一个fulfilled的结果作为结果,如果全都rejected,则报错 AggregateError

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('hi error')
  }, 1000);
})

const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('hi panda')
  }, 2000);
})

Promise.any([promise1, promise2])
       .then(res => console.log(res))
       .catch(e => console.log(e))
       
//hi panda

三、生成器+Promise解决回调地狱

function requestData(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url.includes('iceweb')) {
        resolve(url)
      } else {
        reject('请求错误')
      }
    }, 1000);
  })
}

function* getData(url) {
  const res1 = yield requestData(url)
  const res2 = yield requestData(res1)
  const res3 = yield requestData(res2)

  console.log(res3)
}

const generator = getData('iceweb.io')

generator.next().value.then(res1 => {
  generator.next(`iceweb.org ${res1}`).value.then(res2 => {
    generator.next(`iceweb.com ${res2}`).value.then(res3 => {
      generator.next(res3)
    })
  })
})

//iceweb.com iceweb.org iceweb.io

再进行异步自动化封装,解决上方的回调地狱

function requestData(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url.includes('iceweb')) {
        resolve(url)
      } else {
        reject('请求错误')
      }
    }, 1000);
  })
}

function* getData() {
  const res1 = yield requestData('iceweb.io')
  const res2 = yield requestData(`iceweb.org ${res1}`)
  const res3 = yield requestData(`iceweb.com ${res2}`)

  console.log(res3)
}

//自动化执行 async await相当于自动帮我们执行.next
function asyncAutomation(genFn) {
  const generator = genFn()

  const _automation = (result) => {
    let nextData = generator.next(result)
    if(nextData.done) return

    nextData.value.then(res => {
      _automation(res)
    })
  }

  _automation()
}

asyncAutomation(getData)

//iceweb.com iceweb.org iceweb.io

回调地狱最终解决办法

function requestData(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url.includes('iceweb')) {
        resolve(url)
      } else {
        reject('请求错误')
      }
    }, 1000);
  })
}

async function getData() {
  const res1 = await requestData('iceweb.io')
  const res2 = await requestData(`iceweb.org ${res1}`)
  const res3 = await requestData(`iceweb.com ${res2}`)

  console.log(res3)
}

getData()

//iceweb.com iceweb.org iceweb.io

我们发现, 把getData生成器函数改成async函数,yield关键字替换为await,就和上方的生成器代码类似。async await的核心代码就是和上方的 generatror+promise类似。

四、async/await

1. async异步函数

async关键字用于定义一个异步函数

await用于控制函数的执行,只有当await后面的 thenable对象的状态变为resolved或者rejected(需要捕获异常,否则会报错)后才会继续执行。

async function sayHi() {
  console.log('hi ice')
}

sayHi()

//hi ice

异步函数的返回值

  • 返回值类型确定:是一个Promise
  • 如果函数内返回的值是一个普通的值,那么返回的Promise相当于 Promise.resolve的返回值
  • 如果函数内返回的值是一个thenable对象,那么返回的Promise的状态与then中调用的resolve或者reject有关。
  • 如果明确返回一个Promise,那么返回的Promise的状态由明确返回的Promise决定。

异步函数的异常处理

  • 如果函数内部发生错误,可以在调用处用try catch捕获
  • 如果函数内部发射管错误,也可用函数的返回值.catch进行捕获(本质上返回值是Promise)
async function sayHi() {
  console.log(res)
}
sayHi().catch(e => console.log(e))

//或者

async function sayHi() {
  try {
    console.log(res)
  }catch(e) {
    console.log(e)
  }
}

sayHi()

//ReferenceError: res is not defined

2. await关键字

只有异步函数中才能用await关键字

await关键字特点

  • await通常后面跟的是Promise对象。
  • await后面可以跟普通值、thenable、Promise
  • 只有后面的状态变为fulfilled才会执行下方的代码,否则会等待后方的代码执行完才能向下执行
  • 如果await后的代码状态变为rejected,那么需要在对await代码进行 try catch捕获,或者进行 .catch操作

参考文章:juejin.cn/post/714430…