promise--红宝书

期约


期约基础

  1. 期约是一个带有状态的对象,pedding(等待),resolved(已解决),rejected(拒绝)
  2. 期约的状态单向流动,状态的转移不可逆,也就是说当状态从pedding变为resolved之后,就算调用reject(),状态也不会流向rejected。
  3. 期约的状态是私有的,不可以被外部的js所访问和改变。期约将异步的代码封装起来,以隔绝外部的的同步代码
  4. 期约状态是私有的,只能在内部的执行器中进行操作(改变期约的状态)。执行器的作用有二。
    1. 初始化期约的异步操作
    2. 通过执行函数的函数参数(resolve和reject)来改变期约的状态,resolve改期约为完成,reject改期约为拒绝

期约的执行器函数,是同步的。

因为执行函数需要初始化异步操作,所以执行函数的内部其实是同步操作。包括调用resolve/reject改变期约的状态的这异步操作。

这与await不同,await会堵塞就异步函数的执行,交出js的运行线程。所以await等待后面的promise完成,本身就是一个异步操作。

javascript 复制代码
const fn = async()=>{
    const p = new Promise((resolve)=>{
        console.log(4)
        resolve(9)
    })
    console.log(p)
    console.log(3)
}
console.log(1)
fn()
console.log(2)
// 按照顺序执行
  1. 同步异步的二元性:
    1. 期约是同步的对象,也是异步执行模式的媒介。同步的try/catch无法捕获异步模式的错误(reject())。
    2. 异步模式下的操作(不止promise,包括一些网络的操作,和定时器等)的回调和执行以及错误的处理方法,都不是由js本身去执行的。它们是由浏览器运行时放入消息队列去调用的。所以我们无法捕获异步的错误。也就是说,要捕获异步的错误必须通过异步的方法。(onrejected)

注意,我们这里的异步错误指的是,promise的函数参数reject(),暴露出来的异步错误。只能通过onrejected,让浏览器去回调,我们自己无法回调。

await promise实例,这种暴露出的错误是已经被await"解包"了,并丢出一个同步的错误了,所以此时,我们需要用try/catch来捕获,否则就像在异步函数中丢住一个同步错误一样,会使得异步函数返回一个拒绝的promise。

期约的实例方法

ps:期约的实例方法是连接内部异步代码和外部同步代码的桥梁。

  1. Promise.resolve().then(onResolved(),onRejected() ),期约状态改变时的处理方法。会返回一个已解决的期约实例(resolved),但是抛出异常会返回一个拒绝的期约。
    1. onResolved(value),处理期约状态为成功时的回调。参数为期约执行函数(resolve)的值
    2. onRejected(reason),处理期约状态为失败的回调,并给出理由
    3. 注意:无论期约状态为成功或者失败的处理程序,then处理函数, 都会返回一个已解决的期约对象(除非then执行函数中主动返回pedding状态的promise) 。这里的rejected状态仍然返回一个已解决的期约可能会有点反直觉,但是onrejected的处理函数,本质上就是为了捕获异步的错误,所以也不应该在捕获之后再抛出一个拒绝的期约(异步错误)
    4. then中抛出异常会返回一个拒绝的期约。
javascript 复制代码
const p = Promise.resolve()
const p1 = p.then(()=>{throw(12)}) 
// 返回p1:rejected
const p2 = p.then(()=>Error(12)) 
// 返回 p2:resolved(Error(12)),
//返回一个解决的期约,并把错误包装起来
  1. catch(()=>{}),内部是处理拒绝期约的回调,本质上就是语法糖,和调用then(...,()=>{})的onRejected()回调一样
  2. finally(),处理期约落定之后的回调。
    1. 无论是拒绝还是解决的期约都会执行。但是finally,无法区分期约的状态,所以它主要还是用来清理数据,收尾工作。
    2. 因为finally被设计为一个状态无关的方法,所以它大多是时候状态将是父期约状态的传递。
javascript 复制代码
const p = Promise.resolve(1)
const p1 = p.finally(()=>2)
console.log(p1) // Promise<resolved> 1

期约的非重入特性:

上面说到期约的执行函数用来初始化异步操作,改变期约的状态。所以这个执行函数其实是同步的,比方说。

const p = new Promise((resolve)=>resolve(12)) ,其实实例p已经是一个解决的期约了,但是它的处理程序(then,catch,finally)+ await 不会进入同步线程,而是异步执行的。其实 这些处理程序都是被推入浏览器/node的消息队列执行的。他们是由js运行时保证的

拒绝期约处理+拒绝错误处理

拒绝期约类似于throw表达式,代表了程序应该中断执行。

一般来说,我们在同步代码中抛出错误,会导致程序的中断执行,但是在期约处理函数中抛出同步错误(throw),其实是在消息队列中异步抛出错误,并不会影响同步代码的执行。

我们可以通过try/catch来捕获这些错误,防止期约状态落定。

  1. 如果给期约添加了多个处理程序,当期约状态变化时,将会按照顺序执行。
  2. Promise.all(),Promise.race()
    1. 他们都接受一个可迭代的对象,内部放promise实例或其他(会被当成已经解决的期约),返回一个合成promise实例
    2. Promise.all()的状态是根据所有内部迭代对象决定的,内部全部的期约都解决时,返回的合成期约也显示为解决,并返回一个按顺序包装实例们reoslve值的数组。
    3. 当Promise.all中有一个实例表示拒绝,合成期约对象的状态也是拒绝。并且拒绝实例的reason也会变为合成期约实例的reason。
    4. Promise.race,合成期约对象的状态由最先落定的期约实例决定。当有期约对象落定后,后面所有的落定都会被忽略
    5. 注意:Promise.all 和 Promise.race 都是会静默处理第一个拒绝状态之后的所有拒绝期约,不会抛出异步的错误。
    6. 这也表明,当合成期约落定时候,后续的期约不是不执行了,其实后续的期约的执行函数还是会在消息队列中被取出执行,仅仅是他们的状态被合成期约忽略了而已,我们无法查看。

async /await


  1. async 让函数具有异步特征,但内部按照同步代码执行,
    1. async函数总是期待返回一个期约对象(不一定是已经解决的) ,如果返回值不是期约对象,就包装成已解决的期约,无返回则为resolve(undefined)
    2. 当然,当async函数内部抛出错误,函数返回一个拒绝的期约,理由为抛出的错误对象
    3. 因为async内部(期约的执行函数同理),事实上是同步的,所以我们无法捕获异步错误并把错误包装成拒绝promise对象返回,只能探测到同步throw之类抛出的错误,并把错误理由包装为期约的拒绝理由
javascript 复制代码
const p = async ()=>{
  Promise.reject()
}
console.log(p) // pedding状态
javascript 复制代码
const p = async ()=>{
  throw(3)
}
p().catch(console.log) // 3
// 此时p为rejected的期约
javascript 复制代码
const p = async ()=>{
  await Promise.reject(12) 
}
p().then(null,(e)=>console.log(e))  // √
  1. await会堵塞异步代码的执行,直至期约解决
    1. await 堵塞异步代码执行,交出js运行时,此时js会往下执行异步函数后面的代码
    2. 无论是等待会抛出错误的同步操作,还是等待拒绝的期约,都会取出error(unwrap)作为理由,返回拒绝期约
    3. (await+ throw(err) || await + Promise.reject(err) )==>异步函数返回拒绝期约,并带上错误的理由。
  1. await发生了什么
    1. 在async/await中,真正发挥作用的是await函数,async仅仅是异步函数的标识符而已

    2. 当异步代码执行遇到await,js会记录位置并停止异步函数的执行,等到await后面的值可用了就会向运行时消息队列中推入函数,恢复异步函数的执行,所以即使await后面的值立即可用,也会异步求值。

    3. 当await + 异步期约 ,本质上是进行了两次异步操作,第一次是异步期约的执行,第二次是唤醒当前异步函数。可以当作await本身就是一种异步操作。


注意

promise最主要的是,promise的初始化+改变状态都是同步代码,但是它的then,catch之类的执行函数都是异步的,他们的回调都会被推入消息队列中等待执行,而promise.reject()主动改变promise的状态,还是在初始化异步代码中抛出同步的错误,都会使promise的状态变为拒绝,抛出异步的错误,所以我们无法用同步的try/catch捕获,必须统统onRejected回调来处理

await,需要注意他会堵塞异步代码的执行,无论await后面的值是不是立即可可用。

ps:如果对本文内容有疑问,欢迎私信评论~

相关推荐
进取星辰43 分钟前
33、魔法防御术——React 19 安全攻防实战
前端·安全·react.js
小赖同学啊1 小时前
深度解析 Element Plus
前端·javascript·vue.js
二十雨辰1 小时前
[CSS3]百分比布局
前端·html·css3
大大。1 小时前
Vue3 与 Vue2 区别
前端·面试·职场和发展
EndingCoder1 小时前
从零基础到最佳实践:Vue.js 系列(3/10):《组件化开发入门》
前端·javascript·vue.js
职场马喽1 小时前
vue+luckysheet导出功能(解决了样式为null的报错问题)
前端·javascript·vue.js
北辰浮光1 小时前
[Vue]路由基础使用和路径传参
前端·javascript·vue.js
難釋懷1 小时前
Vue 简介
前端·javascript·vue.js
阿珊和她的猫2 小时前
Axios创建实例:灵活配置和模块化开发
前端·javascript
NoneCoder2 小时前
JavaScript 性能优化:调优策略与工具使用
前端·javascript·面试·性能优化