promise知识点

promise 出现的原因

promise的出现,就是为了在异步请求时解决回调地狱。 回调地狱会导致代码臃肿、可读性差、代码复用率低、只能在回调里面处理异常等等。

什么是promise

Promise 是异步编程的一种解决方案,比传统的异步解决方案【回调函数】和【事件】更合理、更强大。现已被 ES6 纳入进规范中。

promise的使用

Promise是一个构造函数,new Promise返回一个promise对象,他接受两个参数:resolve,reject

const promise = new Promise((resolve,reject)=>{
    //异步处理
    //处理结束后,调用resolve或者reject
})

promise的三种状态:

  1. panding
  2. fulfilled(resolve)
  3. rejected(reject)

promise的状态是不可逆的,只能从panding=>fulfilled/rejected

promise实例对象的方法

promise.then

在promise被resolve的时候触发then

promise.catch

在promise被reject的时候触发

promise的静态方法

Promise.resolve

Primise.resolve返回一个fulfilled状态的promise对象

Promise.resolve('hello')
//相当于
new Promise(resolve=>{
    resolve('hello')
})

Promise.reject

Promise.reject返回一个rejected状态的promise对象

Promise.reject(24)
//相当于
new Promise((resolve,reject)=>{
    reject(24)
})

Primise.all

Promise.all 接受一个promise对象数组 执行过程中,有一个promise返回了reject就会走进catch

const p1 = new Promise((resolve,reject)=>{
    resolve(1)
})
const p2 = new Promise((resolve,reject)=>{
    resolve(2)
})
const p3 = new Promise((resolve,reject)=>{
    resolve(3)
})
Promise.all([p1,p2,p3]).then(data=>{
    console.log(data) //[1,2,3]
}).catch(err=>{

})

Promise.all中的结果顺序和传入Promise.all的数组的顺序有关,但是执行顺序不是,他们是并行执行的。在传入的数组中,有一个promise进了catch,整个promise就会丢弃之前resolve而返回第一个失败的Promise的结果

Promise.race

race翻译过来就是赛跑的意思,和Promise.all一样,接受一个promise对象数组。

在运行中,只要有一个promise的状态从panding变成了fulfilled/rejected,那就返回这个promise的结果

promise的执行顺序

promise有一个非常重要的规则:Promise的构造函数是同步执行的,而promise.then中的函数是异步执行的。

下面摘抄了掘金上的10道题目,并根据自己的理解做出解释。

题目1

const promise = new Promise((resolve, reject) => {
  console.log(1)
  resolve()
  console.log(2)
})
promise.then(() => {
  console.log(3)
})
console.log(4)

运行结果:

1
2
4
3

解释:promise的构造函数是同步执行,所以先执行了 console.log(1) console.log(2) console.log(4)

执行完同步任务后,开始执行异步任务: console.log(3)

题目2

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 1000)
})
const promise2 = promise1.then(() => {
  throw new Error('error!!!')
})

console.log('promise1', promise1)
console.log('promise2', promise2)

setTimeout(() => {
  console.log('promise1', promise1)
  console.log('promise2', promise2)
}, 2000)

运行结果:

promise1:Promise {<panding>}
promise2:Promise {<panding>}
Uncaught (in promise) Error: error!!!
promise1:Promise {<resolved>:'success'}
promise2:Promise {<rejected>:Error: error!!!}

解释: 先执行同步函数:两个console.log

因为promise1和promise2都是一个promise实例,他们的初始状态都是panding

在1000毫秒后,promise1中的then开始执行,而promise2刚好是promise1的then方法,所以Promise2开始执行throw new Error('error!!!')

在2000毫秒后,promise1和promise2的状态都已经在1000毫秒的时候从panding分别变成了resolved/rejected

题目三

const promise = new Promise((resolve, reject) => {
  resolve('success1')
  reject('error')
  resolve('success2')
})

promise
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })

运行结果:

then:success1

解释: Promise从panding变成resolved或者rejected的时候是不可逆的。所以promise已经resolve('success1'),状态已经确定了。

题目四

Promise.resolve(1)
  .then((res) => {
    console.log(res)
    return 2
  })
  .catch((err) => {
    return 3
  })
  .then((res) => {
    console.log(res)
  })

运行结果:

1
2

解释: promise支持链式调用,then后return的是一个新的promise,并把结果传给下一个promise

题目5

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('once')
    resolve('success')
  }, 1000)
})

const start = Date.now()
promise.then((res) => {
  console.log(res, Date.now() - start)
})
promise.then((res) => {
  console.log('到我了')
  console.log(res, Date.now() - start)
})

运行结果:

once
success,现在的时间戳-之前的时间戳,结果应该大于1000毫秒
success,现在的时间戳-之前的时间戳,结果应该大于1000毫秒

解释: 执行顺序是

  1. console.log('once')
  2. 1000毫秒后执行resolve('suceess'),
  3. promise.then因此被调用,按照顺序执行console.log(res, Date.now() - startconsole.log('到我了')console.log(res, Date.now() - start)

题目6

Promise.resolve()
  .then(() => {
    return new Error('error!!!')
  })
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })

运行结果:

then: Error: error!!!

解释:.then 或者 .catch 中 return 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获,需要改成其中一种:

return Promise.reject(new Error('error!!!'))
throw new Error('error!!!')

因为返回任意一个非 promise 的值都会被包裹成 promise 对象,即 return new Error('error!!!') 等价于 return Promise.resolve(new Error('error!!!'))。

题目7

const promise = Promise.resolve()
  .then(() => {
    return promise
  })
promise.catch(console.error)

运行结果:

TypeError: Chaining cycle detected for promise #<Promise>

解释:解释:.then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环。

题目8

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)

运行结果:

1

解释: 我们看到then应该是一个函数而不是一个值,then 中的函数一定要 return 一个结果或者一个新的 Promise 对象,才可以让之后的then 回调接收。 如上传入非函数则会发生值穿透。

题目9

Promise.resolve()
  .then(function success (res) {
    throw new Error('error')
  }, function fail1 (e) {
    console.error('fail1: ', e)
  })
  .catch(function fail2 (e) {
    console.error('fail2: ', e)
  })

运行结果:

fail2: Error: error

解释:.then 可以接收两个参数,第一个是处理成功的函数,第二个是处理错误的函数。.catch 是 .then 第二个参数的简便写法,但是它们用法上有一点需要注意:.then 的第二个处理错误的函数捕获不了第一个处理成功的函数抛出的错误,而后续的 .catch 可以捕获之前的错误。当然以下代码也可以:

Promise.resolve()
  .then(function success1 (res) {
    throw new Error('error')
  }, function fail1 (e) {
    console.error('fail1: ', e)
  })
  .then(function success2 (res) {
  }, function fail2 (e) {
    console.error('fail2: ', e)
  })

题目十

process.nextTick(() => {
  console.log('nextTick')
})
Promise.resolve()
  .then(() => {
    console.log('then')
  })
setImmediate(() => {
  console.log('setImmediate')
})
console.log('end')

运行结果:

end
nextTick
then
setImmediate

解释:process.nextTick 和 promise.then 都属于 microtask,而 setImmediate 属于 macrotask,在事件循环的 check 阶段执行。事件循环的每个阶段(macrotask)之间都会执行 microtask,事件循环的开始会先执行一次 microtask。

执行顺序可简单总结为:process.nextTick()>Promise.then()>setTimeout>setImmediate。

补充

第十题直接摘抄过来,但并没有理解,下面打算开一个专题,专门讨论下Node.js/js的运行机制

感谢掘金网以下链接:

  1. Promise原理讲解
  2. Promise 必知必会
  3. 面试精选之Promise

本文参考如上文章并摘录做出总结,如有疑惑可以在下方评论留言或者去原贴拜读。