前端异步系列(二)-Promise

90 阅读5分钟

1. 什么是Promise

Promise是ES6(2015年)推出的新语法,所有现代浏览器都支持Promise,可以通过 new 操作符来实例化,是主导性的异步编程机制。

以下是自己对Promise的基本概念理解与总结,不喜勿喷。

  1. Promise本身不是异步,它只是连接同步和异步的一个桥梁,它只是处理异步计算的编程模式,它只是一个ES6的一个API
  2. Promise用于异步计算,将异步操作队列话,按照期望的顺序执行,返回符合预期的结果
  3. Promise有三种状态,只能从 (pedding到resolve) 或者从 (pedding到reject),状态不可逆
  4. resolve触发then方法的回调,reject触发catch方法的回调
  5. try/catch的catch无法直接捕获reject抛出的错误,捕获错误需要配合async/await
  6. Promise中抛出的reject错误会阻止当前reject方法体后面的程序执行,但是不会阻止catch的执行
  7. new Promise(() => {}) 永远是pending状态

2. 一个简单的Promise示例执行过程

输出顺序:12635

new Promise((resolve, reject) => {
  console.log(1)
  resolve('成功')
  console.log(2)
}).then(res => {
  console.log(3)
  reject('失败')
  console.log(4)    // 不会执行
}).catch(err => {
  console.log(5)
})
console.log(6)

简单解释一下12635输出顺序

  1. 126属于同步代码,所以会先依次输出126
  2. 同步代码执行到resolve('成功')时,此时会把then方法推送到异步调用栈中,等到同步代码执行完成在执行异步调用栈(也就是then方法)
  3. 执行then方法,输出3,在执行到reject('失败')报失败,reject方法体后面的程序不在执行(所以4不会输出),直接触发catch方法,最后输出5

3. Promise.resolve()、Promise.reject()

Promise.resolve()、Promise.reject()是Promise提供的两个静态方法

  1. 作用是实例化一个期约,也可以理解为实例化一个成功/失败的Promise对象
  2. 以下p1和p2两个Promise实例实际上是一样的,只有在需要处理复杂逻辑的时候才需要使用new Promise()对象,一般推荐使用Promise.resolve()
  3. 如果Promise.resolve()本身传入的参数也是一个期约,那这个行为就类似于空包装,所以说Promise.resolve()是一个幂等方法
  4. Promise.resolve()可以包装任何值,包括Promise.resolve(null), 返回null的Promise对象
  5. 如果没有提供这个处理程序,则Promise.resolve()就会包装上一个期约解决之后的值。如果没有显式的返回语句,则Promise.resolve()会包装默认的返回值undefined的Promise对象。

p1和p2相等

// p1和p2都返回undefined的Promise对象
const p1 = new Promise((resolve) => resolve())  // Promise <resolved>: undefined 
const p2 = Promise.resolve()                    // Promise <resolved>: undefined 

幂等

// 如下三等式都是返回一个Promise对象,成功/失败的结果都是7,这个可以叫做幂等方法
Promise.resolve(7) == Promise.resolve(Promise.resolve(7)) == Promise.resolve(Promise.resolve(Promise.resolve(7))) // Promise {<fulfilled>: 7}
Promise.reject(7) == Promise.reject(Promise.reject(7)) == Promise.reject(Promise.reject(Promise.reject(7))) // Promise {<rejected>: 7}

返回Promise对象

let p1 = Promise.resolve('foo');                // Promise <resolved>: foo 
let p2 = p1.then();                             // Promise <resolved>: foo 
let p3 = p1.then(() => undefined);              // Promise <resolved>: undefined 
let p4 = p1.then(() => {});                     // Promise <resolved>: undefined 
let p5 = p1.then(() => Promise.resolve());      // Promise <resolved>: undefined 
let p6 = p1.then(() => 'bar');                  // Promise <resolved>: bar 
let p7 = p1.then(() => Promise.resolve('bar')); // Promise <resolved>: bar

4. Promise.prototype.then()、Promise.prototype.catch()

  1. then方法可以接受两个参数,也可以接受一个参数,如果是两个参数:第一个参数是成功时的回调,第二个参数是失败时的回调; 如果是一个参数:只是成功时的回调
  2. Promise.prototype.then(null, onRejected) 等价于 Promise.prototype.catch()
  3. p1.then(() => onResolved('p1'), () => onRejected('p1')) 等价于 p1.then(() => onResolved('p1')).catch(() => onRejected('p1'))
function onResolved(id) {
  setTimeout(console.log, 0, id, 'resolved');
}
function onRejected(id) {
  setTimeout(console.log, 0, id, 'rejected');
}

const p1 = new Promise((resolve, reject) => setTimeout(resolve, 3000));
const p2 = new Promise((resolve, reject) => setTimeout(reject, 3000));

// 如下两种写法实现的功能是一样的
p1.then(() => onResolved('p1'), () => onRejected('p1'));        // p1 'resolved'
p2.then(() => onResolved('p2')).catch(() => onRejected('p2'));  // p2 'rejected'

5. Promise.prototype.finally()

从pedding到resolved或者rejected任何状态结束时,finally方法都会执行,但是finally没办法知道当前是resolved还是rejected

const p1 = Promise.resolve();
const p2 = Promise.reject();
let onFinally = function() { 
  setTimeout(console.log, 0, 'Finally!') 
}
p1.finally(onFinally)
p2.finally(onFinally)

6. 非重入期约方法

红宝书上名词“非重入期约方法”

  1. 当期约进入落定状态(.then或.catch),该状态相关的处理程序(回调)会推进调用栈(eventloop),而非立即执行
  2. 非重入适用于.then、.catch、.finally的回调
let synchronousResolve; 
let p = new Promise((resolve) => { 
  synchronousResolve = function() { 
    console.log('1: invoking resolve()'); 
    resolve(); 
    console.log('2: resolve() returns'); 
  }; 
}); 
p.then(() => console.log('3: then() handler executes')); 
synchronousResolve(); 
console.log('4: synchronousResolve() returns'); 

// 实际的输出顺序:
// 1: invoking resolve() 
// 2: resolve() returns 
// 4: synchronousResolve() returns 
// 3: then() handler executes

7. 拒绝期约与拒绝错误处理

  1. Promise的回调里面抛出Error的错误就会使状态pedding到rejected, 即使不主动触发reject()方法
  2. 正常情况下抛出error错误会阻塞程序的运行,但是在Promise里面抛出错误,实际是从消息队队列里面抛出的错误,并不会阻止同步的程序的运行
const p1 = new Promise((resolve, reject) => {throw new Error('peddiing->rejected')})  // Promise rejected
console.log(111) // 仍会执行

8. 期约连锁与期约合成

每个期约实例的方法then()、catch()、finally()都会返回一个新的期约对象(默认是resolved的promise对象),而这个新的期约实例又有自己的实例方法,就会链式调用

const p = new Promise((resolve) => resolve())
// 111、222、333都会输出
p.then(() => console.log(111)) 
 .then(() => console.log(222))
 .then(() => console.log(333))
 .catch((err) => console.log(err))

9. Promise.all([])和Promise.race()

  1. 将多个期约实例组合成一个期约
  2. Promise.all([])数组至少要包含一个Promise,
    • 2.1 数组输出
    • 2.2 一次reject会导致整个Promise.all([])程序到达rejected状态, 且只会输出reject的值,resolve的值将忽略
  3. Promise.race([])是一组期约集合最先resolve或reject的期约镜像,不常用不做过多解释 Promise.all
const p1 = Promise.all([
  Promise.resolve(3), 
  Promise.resolve(),
  Promise.resolve(4)
])
p1.then(res => console.log(res))   // [3, undefined, 4]

Promise.race

const p2 = Promise.all([
  Promise.resolve(3), 
  Promise.reject('err'),
  Promise.resolve(4)
])  
p2.then(res => console.log('res', res))   // 不执行
  .catch(err => console.log('err', err))  // 执行输出err