liangbus / blogging

Blog to go
10 stars 0 forks source link

Promise 相关 #9

Open liangbus opened 5 years ago

liangbus commented 5 years ago

基础知识点就不过多描述了,主要还是通过问题去发掘一些知识点

1. 关于 Promise 状态的理解

以下代码输出什么

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

结果:
1
2
3
4

resolve 完之后的代码也会执行,但是 resove 之后,Promise 的状态已从 pending 改为 resolved,关于状态的转换,Promise 的 A+ 规范上面有相应描述

A promise must be in one of three states: pending, fulfilled, or rejected.

  • When pending, a promise: may transition to either the fulfilled or rejected state.
  • When fulfilled, a promise: must not transition to any other state. must have a value, which must not change.
  • When rejected, a promise: must not transition to any other state. must have a reason, which must not change.

即状态一旦更改,即不可逆,也不可变 所以即使 reject 了也不会进入 catch 方法

题目拓展 >>

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 0)
})
const p2 = p1.then(() => {
    throw new Error('something wrong!')
})
console.log(`p1 > `, p1)
console.log(`p2 > `, p2)

setTimeout(() => {
    console.log(`p1 > `, p1)
    console.log(`p2 > `, p2)
}, 0)

VM8247:9 p1 >  Promise {<pending>}
VM8247:10 p2 >  Promise {<pending>}
VM8443:7 Uncaught (in promise) Error: something wrong!
    at <anonymous>:7:8
p1 >  Promise {<resolved>: "success"}
VM8443:14 
p2 >  Promise {<rejected>: Error: something wrong!
    at <anonymous>:7:8}
liangbus commented 5 years ago

2. 假如有一个 Promise 在构造函数里面 throw 一个错误,然后后面先 catch 再执行 then, 那么 then 方法会被执行吗?为什么

new Promise(() => {
    throw Error('something happended!')
}).catch(err => {
    console.log(`err > `, err)
}).then(() => {
    console.log(`I am in then func`)
})
// 执行结果
err >  Error: something happended!
    at <anonymous>:2:8
    at new Promise (<anonymous>)
    at <anonymous>:1:1
VM425:6 I am in then func
Promise {<resolved>: undefined}

这是有一次面试遇到的一个问题,感觉这么问,结果大概率是会执行的,但是并不知道为什么[cry]

从结果导向,既然执行了 then ,那就看执行完 catch 方法后,返回是什么就知道了

new Promise(() => {
    throw Error('something happended!')
}).catch(err => {
    console.log(`err > `, err)
})
VM536:4 err >  Error: something happended!
    at <anonymous>:2:8
    at new Promise (<anonymous>)
    at <anonymous>:1:1
Promise {<resolved>: undefined}

注意这里返回结果是一个 Promise ,并且其状态是 resolved !!

仅执行构造函数,返回结果如下

new Promise(() => {
    throw Error('something happended!')
})
Promise {<rejected>: Error: something happended!
    at <anonymous>:2:8
    at new Promise (<anonymous>)
    at <anonymo…}

状态为 rejected 的 Promise

MDN 的解释是这样子的

pending 状态的 Promise 对象可能会变为fulfilled 状态并传递一个值给相应的状态处理方法,也可能变为失败状态(rejected)并传递失败信息。当其中任一种情况出现时,Promise 对象的 then 方法绑定的处理方法(handlers )就会被调用(then方法包含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型。当Promise状态为fulfilled时,调用 then 的 onfulfilled 方法,当Promise状态为rejected时,调用 then 的 onrejected 方法, 所以在异步操作的完成和绑定处理方法之间不存在竞争)。 image

至此,该问题就很清晰了

参考: Promise - MDN

liangbus commented 5 years ago

关于 Promise 的链式调用?

下面的代码输出什么?

Promise.resolve(1).then((res) => { 
    console.log('then >> ', res)
    throw new Error('Wrong!!!!')
    return 2
}).catch((err) => {
    console.log('Error >> ', err)
    return 3
}).then((res) => {
    console.log('last then >>> ', res)
})
then >>  1
VM8925:6 Error >>  Error: Wrong!!!!
    at <anonymous>:3:8
VM8925:9 last then >>>  3

MDN 关于 Promise.resolve(value) 的描述

返回一个状态由给定value决定的Promise对象。如果该值是thenable(即,带有then方法的对象),返回的Promise对象的最终状态由then方法执行决定;否则的话(该value为空,基本类型或者不带then方法的对象),返回的Promise对象状态为fulfilled,并且将该value传递给对应的then方法。通常而言,如果你不知道一个值是否是Promise对象,使用Promise.resolve(value) 来返回一个Promise对象,这样就能将该value以Promise对象形式使用。

问题延伸

then 返回的是什么?如果返回一个异常会怎么样?

以下代码

Promise.resolve().then(() => {
    console.log('inside then~')
    return new Error('something WRONG!!')
}).then(res => {
    console.log('inside second then ~', res)
}).catch(err => {
    console.log('inside catch >> ', err)
})
VM260:2 inside then~
VM260:5 inside second then ~ Error: something WRONG!!
    at <anonymous>:3:9
Promise {<resolved>: undefined}

注意这里并不是 throw Error 而是 return Error 因为返回任意一个非 promise 的值都会被包裹成 promise 对象,即 return new Error('error!!!') 等价于 return Promise.resolve(new Error('error!!!'))。 正确触发 catch 的写法:

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

再来看下面代码

Promise.resolve()
  .then(function success (res) {
    throw new Error('error')
  }, function fail1 (err) {
    console.error('fail in reject func: ', err)
  })
  .catch(function fail2 (err) {
    console.error('fail in catch: ', err)
  })

VM10390:8 fail in catch:  Error: error
    at success (<anonymous>:3:11)
Promise {<resolved>: undefined}

可以看出 then 方法中传入的 reject 方法没办法捕获到 resovlve 中抛出的异常,只能由后续的 catch 方法捕获到 再来修改一下看看

Promise.resolve()
  .then(function success (res) {
    throw new Error('error')
  }, function fail1 (err) {
    console.error('fail in reject func: ', err)
  }).then( function success2 (res) {
    console.log('succeed in second success func', res)
  }, function fail2 (err) {
    console.error('fail in second reject func', err)
  })

fail in second reject func Error: error
    at success (<anonymous>:3:11)
Promise {<resolved>: undefined}

此时第一个 then 中抛出的异常,被第二个 then 中的 reject 方法捕获到了 其实原因还是因为 then 执行完之后,返回的 Promise 状态是 rejected,所以导致后面触发的是 reject 方法,同级的 reject 方法无法捕获同级的 resolve 中的异常,这也是 Promise 的局限性之一,也就叫做被吃掉的异常,因为总会有可能存在无法捕获的异常

参考: Promise 必知必会(十道题) - 石墨文档

liangbus commented 4 years ago

练习:用 Promise 实现红绿灯问题 红灯三秒亮一次,绿灯一秒亮一次,黄灯2秒亮一次;如何让三个灯不断交替重复亮灯?

function red(){
  console.log('red');
}
function green(){
  console.log('green');
}
function yellow(){
  console.log('yellow');
}
function lightup(t, fn) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      fn()
      resolve()
    }, t)
  })
}

function round() {
  Promise.resolve().then(() => {
    return lightup(3000, red)
  })
  .then(() => {
    return lightup(1000, green)
  })
  .then(() => {
    return lightup(2000, yellow)
  })
  .then(() => {
    round()
  })
}

采用递归 + setTimeout 方式调用

liangbus commented 4 years ago

最后附上简单版本的 Promise/A+ 手写实现

YuetTong commented 2 years ago

哥哥 你看我这个还有的救吗?除了开头那6秒多等,我想clearinterval当第一次执行时不等待,貌似不行。呜呜呜。。。。。。 function red() { console.log('red'); } function green() { console.log('green'); } function yellow() { console.log('yellow'); } let i = 0 setInterval(() => { if (i === 0) { clearInterval() } setTimeout(() => { red() setTimeout(() => { green() setTimeout(() => { yellow() }, 1000); }, 2000); }, 3000); i++ }, 6000);