JavaScript-Promise相关个人总结(Promise、async与await、JS异步回调的宏队列及微队列、Promise面试题)

Promise

- 基本使用(new Promise)

// promise基本使用

// 1. 创建一个promise对象
const p = new Promise((resolve, reject)=>{ // 执行器函数
  // 2. 执行异步操作任务
  setTimeout(()=>{
    const time = date.now() // 当前时间为偶数就成功,奇数就失败
    if(time % 2 === 0){
      // 3.1. 如果成功,调用resolve(value)
      resolve('成功,数据为' + time)
    }else {
      // 3.2. 如果失败,调用reject(reason)
      reject('失败,数据为' + time)
    }
  }, 1000)
  
})

p.then(
  value => { // 接收得到成功的value数据  onResolved
    console.log('成功回调', value)
  },
  reason => { // 接收得到成功的reason数据  onRejected
    console.log('失败回调', reason)
  }
)

- Promise的resolve及reject方法(创建Promise对象的语法糖)

// 普通方式创建一个成功值为1的promise对象
const p1 = new Promise((resolve, reject)=>{
  resolve(1)
})

// Promise.resolve方法创建一个成功值为2的promise对象
const p2 = Promise.resolve(2)
// Promise.reject方法创建一个失败值为3的promise对象
const p3 = Promise.reject(3)

// then方法,包含onResolved和onRejected
p1.then(
  value => {
    console.log(value) // 1
  },
  reason => {}
)
// then方法,只回调onResolved
p2.then(value => {console.log(value)}) // 2
// catch方法,可以直接回调onRejected
p3.catch(reason =? {console.log(reason)}) // 3

- Promise.all(成功则返回所有promise对象的成功值,失败则返回失败对象的失败值)

Promise的all方法需要传入一个数组,数组内所有元素必须都为promise对象
all方法返回的也是一个promise对象,该对象可能会有成功或失败两种状态及对应的值

  • 成功条件:当数组内所有promise对象都为成功时,返回成功状态
  • 成功的值:一个依次按顺序包含所有promise对象成功值的数组
    ·
  • 失败条件:数组内任一promise对象状态为失败
  • 失败的值:数组内状态为失败的那个promise对象所对应的失败值
// 创建3个promise对象,p1成功,p2成功,p3失败
const p1 = Promise.resolve(1)
const p2 = Promise.resolve(2)
const p3 = Promise.reject(3)

// pAll,只传入p1和p2
const pAll = Promise.all([p1, p2])
// pAll2,传入p1、p2、p3
const pAll2 = Promise.all([p1, p2, p3])
// 由于pAll内的p1和p2都为成功,因此可成功输出所有成功值
pAll.then(values => {console.log(values)}) // [1, 2] 成功值的顺序和传入all方法内数组内的元素顺序一致
// pAll2内的p3是失败,因此输出结果为p3的失败值
pAll2.catch(reason => console.log(reason))

- ES11新增 Promise.allSettled(无论其中promise对象成功或失败,返回所有promise对象的返回状态及值)

执行allSettled方法需要传入一个数组作为参数,数组内每个元素需要为Promise对象
`
allSettled方法执行后返回一个Promise对象,结果有两个值:

  • 返回状态,此状态不受数组中promise对象的状态影响
  • 返回值,一个包含方法中所有promise对象执行结果及返回值的数组
// 声明两个Promise对象
const p1 = new Promise((resolve, reject)=>{
  setTimeout(()=>{
  	resolve('数据1')
  },1000)
})

const p2 = new Promise((resolve, reject)=>{
  setTimeout(()=>{
  	reject('失败了')
  },1000)
})

// 调用 allSettled 方法
const result = Promise.allSettled([p1, p2])
console.log(result)
// [[PromiseState]]: 'fulfilled'
// [[PromiseResult]]: [{status: 'fulfilled', value: '数据1'}, {status: 'rejected', value: '失败了'}]

- Promise.race(返回第一个执行完毕的promise对象结果)

Promise的race方法需要传入一个数组,数组内所有元素必须都为promise对象
race方法返回的是作为其方法参数的数组中,第一个执行完毕的promise对象,该对象的状态及返回值,作为race方法执行后的promise对象返回

// 创建2个promise对象,p1在200毫秒后返回成功结果,p2在100毫秒后返回失败结果
const p1 = new Promise((resolve, reject)=>{
  // 200毫秒后成功
  setTimeout(()=>{
    resolve(1)
  }, 200)
})
const p2 = new Promise((resolve, reject)=>{
  // 100毫秒后失败
  setTimeout(()=>{
    reject(2)
  }, 100)
})

// pRace,传入p1和p2
const pRace = Promise.race([p1, p2])
// 虽然p1比p2的传入顺序更靠前,但p2得到执行结果的速度比p1更快
// 因此pRace会返回p2的结果
pRace.then(
  value => {console.log(value)},
  reason => {console.log(reason)} // 2
)

- Promise的几个关键点

* 改变promise状态

改变promise状态,有3种方式

  • 执行器函数内执行resolve()方法,则状态变为resolved
  • 执行器函数内执行reject()方法,则状态变为rejected
  • 执行器函数内抛出异常 throw … ,则状态变为rejected
// 改变promise状态
// 创建3个promise对象
const p1 = new Promise((resolve, reject)=>{
  resolve(1)
})
const p2 = new Promise((resolve, reject)=>{
  reject(2)
})
const p3 = new Promise((resolve, reject)=>{
  throw '错误' // 主动抛出异常,但是当我们编写出错时,js也会自动抛出异常
})

p1.then(value => {console.log(value)}) // 1
p2.catch(reason => {console.log(reason)}) // 2
p3.catch(reason => {console.log(reason)}) // '错误'

* 指定多个回调

同一个promise对象,指定多个回调函数时,仍然会根据对应状态去调用

// 指定多个回调
// 创建1个promise对象
const p1 = new Promise((resolve, reject)=>{
  resolve(1)
})

// 以下的回调都会根据p1的状态去调用
p1.then(...)
p1.then(...)

* 改变promise状态和回调函数的执行先后顺序

// 改变promise状态和回调函数的执行先后顺序

// 1. 常规情况(先指定回调,后改状态)
new Promise((resolve, reject)=>{ // 1.1. 执行器函数运行
  // 1.2. 执行器函数内有异步操作
  setTimeout(()=>{
	resolve(1) // 1.4. 异步操作得出结果,改变了状态且指定了数据,异步执行回调
  }, 1000)
}).then( // 1.3. 指定回调函数,但由于异步操作未执行完成,会将当前的回调函数保存
  value => {...},
  reason => {...}
)

// 2. 同步执行(先改状态,后指定回调)
new Promise((resolve, reject)=>{ // 2.1. 执行器函数运行
  resolve(1) // 2.2. 改变了状态且指定了数据,异步执行回调
}).then( // 2.3. 指定回调函数,并异步执行回调函数
  value => {...},
  reason => {...}
)

// 3. 特殊情况
const p1 = new Promise((resolve, reject)=>{ // 3.1. 执行器函数运行
  // 3.2. 执行器函数内有异步操作
  setTimeout(()=>{
	resolve(1) // 3.3. 1000毫秒后,改变了状态且指定了数据,异步执行回调
  }, 1000)
})

// 3.4. 1100毫秒后,指定回调函数,并异步执行回调函数
setTimeout(()=>{
  p1.then(
    value => {...},
    reason => {...}
  )
}, 1100)

* then方法返回的promise状态

promise的then方法返回的也是一个promise对象
该promise对象的状态和值,根据then方法中对应的异步回调函数中的执行结果而决定,概括为4点:

  • 不返回任何值,状态为成功,值为undefined
  • 返回一个非promise属性的值,状态为成功,值为返回的那个值
  • 返回一个promise对象(简称p),状态和值为p的执行状态及返回值
  • throw 抛出异常,状态为失败,值为抛出的那个值
    ·

无论是在then的value(成功)还是reason(失败)内,都满足以上说法

// then方法返回的promise状态

// pThen会有几种状态呢?
const pThen = new Promise((resolve, reject)=>{
  resolve(1)
}).then(
  value => {
    // 1. 不返回任何值,状态为成功,值为undefined

    // 2. 返回一个非promise属性的值
    // return 123  // 状态为成功,值为123

    // 3. 返回一个promise对象
    // return new Promise((resolve, reject)=>{
         // resolve(3)  // 状态为成功,值为3
         // reject(4)  // 状态为失败,值为4
    // })

    // 4. throw 抛出异常
    // throw '错误'  // 状态为失败,值为'错误'
  }
)

* then的链式调用

then方法可以帮助我们串联多个操作任务
无论这些任务是异步还是同步,都可以按照then的链式顺序进行执行

// then的链式调用
new Promise((resolve, reject)=>{
  setTimeout(()=>{
    console.log('执行异步1')
    resolve(1)
  }, 1000)
}).then(
  value => {
    console.log('异步1的结果:' + value)
    console.log('执行同步2')
    return 2
  }
).then(
  value => {
    console.log('同步2的结果:' + value)
    return new Promise((resolve, reject)=>{
      setTimeout(()=>{
        console.log('执行异步3')
        resolve(3)
      }, 1000)
    })
  }
).then(
  value => {
    console.log('异步3的结果:' + value)
  }
)

// 以上console.log的输出顺序及结果如下
// '执行异步1'
// ‘异步1的结果:1’
// ‘执行同步2’
// ‘同步2的结果:2’
// ‘执行异步3’
// ‘异步3的结果:3’

* promise的异常传透

// promise的异常传透
new Promise((resolve, reject)=>{
  reject('错误')
}).then(
  value => {...}
).then(
  value => {...}
).catch(
  reason => {console.log(reason)} // '错误'
)
// 上方代码的then内部没有写捕获错误的reason,为何最后的catch能捕获到最初的reject呢

// 在promise内部,其实是相当于这样处理:
new Promise((resolve, reject)=>{
  reject('错误')
}).then(
  value => {...},
  reason => {throw reason} // 写法1:抛出错误,向下一级传递
).then(
  value => {...},
  reason => Promise.reject(reason) // 写法2:返回一个状态为失败且将失败值作为参数的promise对象
).catch(
  reason => {console.log(reason)} // '错误'
)

* 中断promise链

promise链式调用时,如果想在某一步中断,不继续向下传递的话,则在其回调函数内返回一个pending状态的promise对象即可

// 中断promise链

// 需求:catch到错误后,就不要继续往下传递了
new Promise((resolve, reject)=>{
  reject('错误')
}).then(
  value => {...}
).then(
  value => {...}
).catch(
  reason => {
    console.log(reason) // '错误'
    return new Promise(()=>{}) // 返回一个pending状态的promise对象,则promise链在此处中断,不会往后执行
  }
).then(
  value => {...},
  reason => {...}
)

async与await(ES8引入)

- async

async 函数的返回值为 promise 对象
promise 对象的结果由 async 函数执行的返回值决定

// async函数,返回结果是一个Promise对象
// 而Promise对象内的结果(状态、值)由async函数内执行的返回值决定
// 返回值有这么几种情况:
// 1. 非Promise对象
async function fn1(){
  return '字符串'
}
const result1 = fn1()
console.log(result1) // Promise对象状态:resolved,值:'字符串'

// 2. 抛出错误
async function fn2(){
  throw '出错了'
}
const result2 = fn2()
console.log(result2) // Promise对象状态:rejected,值:'出错了'

// 3. Promise对象
async function fn3(){
  return new Promise((resolve, reject)=>{
  	// resolve('成功')
  	// reject('失败')
  })
}
const result3 = fn3()
console.log(result3) // result3的状态与值取决于函数fn3内return的promise执行结果

- await

await 必须写在 async 函数中
await 右侧的表达式返回值一般为 promise 对象,而await返回的是promise成功的值
如果await 右侧的返回值不是promise,则await直接返回那个值本身
await 的 promise 失败了, 就会抛出异常, 需要通过 try…catch 捕获处理

// 创建Promise对象
const p = new Promise((resolve, reject)=>{
  resolve('用户数据')
  // reject('失败了')
})

// await 放在async函数中
async function fn() {
  try{
    // Promise对象成功,执行此处
    let result = await p
    console.log(result) // '用户数据'
  } catch(e) {
    // Promise对象失败,执行此处
    console.log(e)
  }
}

// 调用函数
fn()

JS异步之宏列队与微列队

JS 中用来存储待执行回调函数的队列包含 2 个不同特定的列队

  • 宏列队: 用来保存待执行的宏任务(回调), 比如: 定时器回调/DOM 事件回调 /ajax 回调
  • 微列队 : 用来保存待执行的微任务(回调),比如: promise 的回调 / MutationObserver 的回调
    ·

JS 执行时会区别这 2 个列队

  • JS 引擎首先必须先执行所有的初始化同步任务代码
  • 每次准备取出第一个宏任务执行前, 都要将所有的微任务一个一个取出来执行
// JS异步之宏队列与微队列

// 输出顺序依次为 ’微任务1‘ ’微任务2‘ ’宏任务1‘ ’微任务3‘ ’宏任务2‘

// 宏任务1
setTimeout(()=>{
  console.log('宏任务1')
  // 微任务3
  Promise.resolve(3).then(
    value => {console.log('微任务3')}
  )
}, 0)

// 宏任务2
setTimeout(()=>{
  console.log('宏任务2')
}, 0)

// 微任务1
Promise.resolve(1).then(
  value => {console.log('微任务1')}
)

// 微任务2
Promise.reject(2).catch(
  reason => {console.log('微任务2')}
)

Promise相关面试题

- 面试题1

// Promise面试题1,以下代码的打印输出顺序

setTimeout(()=>{
  console.log(1)
},0)
Promise.resolve().then(()=>{
  console.log(2)
})
Promise.resolve().then(()=>{
  console.log(4)
})
console.log(3)

- 面试题1解析

// Promise面试题1解析,以下代码的打印输出顺序

setTimeout(()=>{ // 宏任务1
  console.log(1)
},0)
Promise.resolve().then(()=>{ // 微任务1
  console.log(2)
})
Promise.resolve().then(()=>{ // 微任务2
  console.log(4)
})
console.log(3) // 同步1

// 执行顺序,同步1 -> 微任务1 -> 微任务2 -> 宏任务1
// 3 2 4 1

- 面试题2

// Promise面试题2,以下代码的打印输出顺序

setTimeout(() => {
  console.log(1)
}, 0)
new Promise((resolve) => {
  console.log(2)
  resolve()
}).then(() => {
  console.log(3)
}).then(() => {
  console.log(4)
})
console.log(5)

- 面试题2解析

// Promise面试题2解析,以下代码的打印输出顺序

setTimeout(() => {
  console.log(1) // 宏任务1
}, 0)
new Promise((resolve) => {
  console.log(2) // 同步1
  resolve()
}).then(() => {
  console.log(3) // 微队列-微任务1,执行完后才会产生微任务2
}).then(() => {
  console.log(4) // 微队列-微任务2
})
console.log(5) // 同步2

// 执行顺序,同步1 -> 同步2 -> 微任务1 -> 微任务1执行完后产生微任务2 -> 微任务2 -> 宏任务1
// 2 5 3 4 1

- 面试题3

// Promise面试题3,以下代码的打印输出顺序

const first = () => (new Promise((resolve, reject) => {
  console.log(3)
  let p = new Promise((resolve, reject) => {
    console.log(7)
    setTimeout(() => {
      console.log(5)
      resolve(6)
    }, 0)
    resolve(1)
  })
  resolve(2)
  p.then((arg) => {
    console.log(arg)
  })

}))

first().then((arg) => {
  console.log(arg)
})
console.log(4)

- 面试题3解析

// Promise面试题3解析,以下代码的打印输出顺序

const first = () => (new Promise((resolve, reject) => {
  console.log(3) // 同步1
  let p = new Promise((resolve, reject) => {
    console.log(7) // 同步2
    setTimeout(() => {
      console.log(5) // 宏任务1
      resolve(6)
    }, 0)
    resolve(1)
  })
  resolve(2)
  p.then((arg) => {
    console.log(arg) // 微任务1
  })

}))

first().then((arg) => {
  console.log(arg) // 微任务2
})
console.log(4) // 同步3

// 执行顺序,同步1 -> 同步2 -> 同步3 -> 微任务1 -> 微任务2 -> 宏任务1
// 3 7 4 1 2 5

- 面试题4

// Promise面试题4,以下代码的打印输出顺序

setTimeout(() => {
  console.log("0")
}, 0)
new Promise((resolve,reject)=>{
  console.log("1")
  resolve()
}).then(()=>{        
  console.log("2")
  new Promise((resolve,reject)=>{
    console.log("3")
    resolve()
  }).then(()=>{      
    console.log("4")
  }).then(()=>{       
    console.log("5")
  })
}).then(()=>{  
  console.log("6")
})

new Promise((resolve,reject)=>{
  console.log("7")
  resolve()
}).then(()=>{         
  console.log("8")
})

- 面试题4解析

// Promise面试题4解析,以下代码的打印输出顺序
// 以下任务先按执行类型+输出数字来命名,最后来分析输出顺序
// 宏:宏任务,同:同步执行,微:微任务

setTimeout(() => { // 宏0
  console.log("0")
}, 0)
new Promise((resolve,reject)=>{
  console.log("1") // 同1
  resolve()
}).then(()=>{ // 微2
  console.log("2")
  new Promise((resolve,reject)=>{
    console.log("3") // 同3
    resolve()
  }).then(()=>{ // 微4
    console.log("4")
  }).then(()=>{ // 微5  
    console.log("5")
  })
}).then(()=>{ // 微6
  console.log("6")
})

new Promise((resolve,reject)=>{
  console.log("7") // 同7
  resolve()
}).then(()=>{ // 微8
  console.log("8")
})

// 执行顺序:
// 第一遍由上而下初始化执行:
//  - 找到宏0,放入宏队列-[0]
//  - 找到同1,输出:1
//  - 找到微2,放入微队列微队列-[2]
//  - 微6需要微2执行后才产生,微6放入缓存
//  - 找到同7,输出:1 7
//  - 找到微8,放入微队列-[2, 8]
// 第一遍初始化执行完成

// 宏队列-[0],微队列-[2, 8],输出:1 7
// 开始执行微队列
//  - 执行微2,微队列-[8],输出:1 7 2
//  - 微2里找到同3,输出:1 7 2 3
//  - 微2里找到微4,加入微队列-[8, 4]
//  - 微2里找到微5,微5需微4执行后才产生,微5放入缓存
//  - 微2执行完毕,产生微6,加入微队列-[8, 4, 6]
//  - 执行微8,微队列-[4, 6],输出1 7 2 3 8
//  - 执行微4,微队列-[6],输出1 7 2 3 8 4
//  - 微4执行完毕,产生微5,加入微队列-[6, 5]
//  - 执行微6,微队列-[5],输出1 7 2 3 8 4 6
//  - 执行微5,微队列全部执行完毕,输出1 7 2 3 8 4 6 5

//  - 由于微队列全部执行完毕,开始执行宏队列
//  - 执行宏0,宏队列全部执行完毕,输出1 7 2 3 8 4 6 5 0

// 最终输出顺序及结果:1 7 2 3 8 4 6 5 0


- Recorded by Scorpio_sky@2021-01-30

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值