Promise 基础

124 阅读7分钟
目的

首先,Promise是ES6新增的内置类(new Promise), 并且Promise 是一个"承诺"设计模式,主要目的是用来解决JS异步编程中的“回调地狱”(有效的管控异步编程).

Promise 三种角色
  1. 作为普通函数
Promise() //   Promise constructor cannot be invoked without 'new'
new Promise() // Uncaught TypeError: Promise resolver undefined is not a function   不能为空
new Promise([executor]) // executor函数 是一个可执行函数
 

2. 作为构造函数

  • 实例: 作为实例的话有两个特殊私有属性的值

    1. [[PromiseState]] promise状态: pending、fulfilled/resolved、 rejected
    2. [[PromiseResult]] promise的值
  • Promise.prototype 原型

    • then
    • catch
    • finally

image.png 3. 作为 普通对象

 +  reject
 +  resolve
 +  all
 +  race

image.png

为什么说Promise是异步呢?
  1. Promise 是用来管控异步编程的,new Promise本身不是异步的,执行它的时候会立即把executor函数执行(只不过我们经常在executor中管控一个异步操作)

  2. resolve/reject :传递给executor函数的参数(参数值都是函数)

  3. promise初始状态是pending 初始值是undefined

  4. resolve([value]) 修改promise状态为fulfilled / resolved 成功状态,并且改变其值为[value]

  5. reject([reason]) 修改promise状态为rejected 失败状态,并且改变其值为[reason]

  6. 一旦状态发生改变,都不能改变为其他状态了

  7. 基于then存放两个回调函数,状态为成功后调用第一个回调函数执行,形参的值就是[[PromiseResult]],状态为失败则调用第二个回调函数执行。。。

  8. Promise中的异步指的是resolve/reject执行

    • 执行两个方法的时候不仅仅是修改状态和值,还要通知then存储的两个回调函数中的一个执行
    • 执行两个方法的时候,需要先等待promise已经基于then把方法存储完毕,有方法后才会去执行

9.如果executor函数执行报错,状态也会变为失败态,并且改变其值为失败的原因

let p = new Promise((resolve,reject)=> {
    // resolve('ok')
    console.log(1)
})
    console.log(2) // 这个的输出顺序依然是 1 2
    
   p.then(value => {
      console.log(‘成功执行’,value);
    }, reason => {
      console.log(‘失败执行’,reason);
    })
then 链
  • 基于.then 注册成功或者失败执行的回调函数,它的返回结果是一个新的promise实例

  • new Promise([executor]) 返回的实例的状态和value是根据resolve/reject执行。再或者 [executor]函数执行是否报错来决定的

  • .then(...) 返回的实例的状态和value是根据 .then注册的两个方法,不论哪一个方法执行,执行的返回结果和是否报错来决定状态和value的。=>不论哪一个方法执行,只要不报错,状态就是成功,报错状态则为失败,方法的返回值(或者报错原因)就是新的实例的value值;

  • 特殊:如果返回的是一个新的promise实例(Promise.reject([reason])),则当前实例的状态和value决定了P2的状态和value

let p = new Promise((resolve, reject) => {
     // resolve('ok')
     reject('NO')
   })
   let p2 = p.then(value => {
     console.log('成功执行p', value);
     console.log(a);
     return '@'
   }, reason => {
     console.log('失败执行', reason);
     return '@@'
   })
   let p3 = p2.then(value => {
     console.log('成功执行p2', value);
     // console.log(a); 这个就是报错 
     
     // 如果有报错,按报错的,
     // 若执行没有报错,而且返回一个新的promise实例,则当前实例的成功和失败,直接影响p3的结果
     return Promise.reject('11')
   }, reason => {
     console.log('失败执行p2', reason);
   })
   p3.then(value => {
     console.log('成功执行p3', value);
   }, reason => {
     console.log('失败执行p3', reason);
   })
   console.log(p)
   console.log(p2)

   // 返回状态是成功或者失败,值时[value]/[reason]
   // Promise.resolve([value])  //  相当于创建个实例 状态是成功态 和下面的一样
   // new Promise(resolve => {
   //   resolve([value])
   // })

   // Promise.reject([reason])

then 链细节小知识

  • 如果状态一旦确定,想去执行.then注入的某个方法,但是此方法没有被注册,则向下顺延(找下一个then中注册的对应方法)

  • throw new Error('AAA')也可以报错

// 因为没有写成功态报的警告
 Promise.reject(100).then(value => {
      console.log('OK', value);
    }
    )

image.png

 // 如果状态一旦确定,想去执行.then注入的某个方法,但是此方法没有被注册,则向下顺延(找下一个then中注册的对应方法)
    Promise.reject(100).then(value => {
      console.log('OK', value);
    }/* 如果没有reason 但其实相当于默认加的这个 所以顺延到下一个then
     reason=> {
      return Promise.reject(reason)
     }
     */
    ).then(null, reason => {
      console.log('NO', reason); // NO 100
      return Promise.resolve(200)
    }).then(null, reason => {
      console.log('NO', reason);
    }).then(value => {
      console.log('OK', value); // OK 200
    })
.catch
  • .catch (reason=> {}) === .then(null,reason=>{}) 等价其实就是两个写法
  • Promise.prototype.catch()方法是Promise.prototype.then(undefined, onRejected)方法的别名,用于指定发生错误时的回调函数。
  • catch方法接受一个参数,该参数是一个函数,拥有一个参数reason,参数的含义是Promise失败的原因
    Promise.reject(100).then(value => {
      console.log('OK', value);
    }).catch(reason => {
      console.log('NO', reason);
    })
.finally

方法用于在Promise成功或拒绝时使用,不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数,它可以防止Promise的then()和catch()方法中的代码重复。

let p = new Promise((resolve,reject) => {
    resolve('resolve')
})
p.then((res) => {
    console.log(res)
}).catch((err) => {
    console.log(err)
}).finally(()=> {
    console.log('this finally')
})

使用场景

  • 平常在工作中经常会遇到一些加载loading的需求,一般都在请求前设置loading展示,请求完成后关闭loading,刚开始我下意识把关闭loading放到then方法里面去处理,发现如果请求出现错误的情况,页面loading是还在的,就需要在catch方法里重写一遍导致代码重复,后面才了解到finally方法可以处理Promise的then和catch方法中代码重复的功能
this.opLoading = true;
api.then((res) => {
	if(res.code == 200){
		// xxx
	}
}).catch((err) => {
	// xxx
}).finally(()=> {
	this.opLoading = false;
})

all
  • all 等待所有promise实例都是成功,整体返回的实例才是成功(都成功整体才是成功,只要有一个失败,就是失败)
成功态例子
function fn1 () {
      return Promise.resolve(1)
    }
    function fn2 () {
      return new Promise((resolve => {
        setTimeout(() => {
          resolve(2)
        }, 2000)
      }))
    }
    function fn3 () {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(3)
        }, 1000)
      })
    }
    Promise.all([fn1(), fn2(), fn3()]).then(values => {
      // values[Array]按顺序存储每一个实例返回的结果
      console.log(values); // [1, 2, 3]
    }).catch(reason => {
      // 一旦有是失败的,整体都是失败,存储的是当前失败这个实例的原因
      console.log('NO', reason);
    })

失败的例子


function fn1 () {
      return Promise.resolve(1)
    }
    function fn2 () {
      return new Promise((resolve => {
        setTimeout(() => {
          resolve(2)
        }, 2000)
      }))
    }
    function fn3 () {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          reject(3)
        }, 1000)
      })
    }
    Promise.all([fn1(), fn2(), fn3()]).then(values => {
      console.log(values); 
    }).catch(reason => {
      // 一旦有是失败的,整体都是失败,存储的是当前失败这个实例的原因
      console.log('NO', reason); //NO 3
    })

课外题# Promise.all哪怕一个请求失败了也能得到其余正确的请求结果的解决方案

race

  • 等待最新有返回结果的promise实例,此实例的成功和失败决定最后的成功和失败(赛跑:谁快听谁的)
 function fn1 () {
      return Promise.resolve(1)
    }
    function fn2 () {
      return new Promise((resolve => {
        setTimeout(() => {
          resolve(2)
        }, 2000)
      }))
    }
    function fn3 () {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(3)
        }, 1000)
      })
    }
    Promise.race([fn1(), fn2(), fn3()]).then(value => {
      console.log('ok', value); //1
    }).catch(reason => {
      console.log('NO', reason);
    })
    如果 fn3 换成失败态 输出快的失败就是3
     Promise.race([ fn2(), fn3()]).then(value => {
      console.log('ok', value); 
    }).catch(reason => {
      console.log('NO', reason); //3
    })

async await

  • async /await 是ES7中提供的,它是对promise的一个补充(promise的语法糖)
  • 用async 修饰一个函数,函数返回的结果都会变成一个promise实例
    • 状态: 大多都是成功的,如果代码执行报错,返回失败,再如果手动返回一个新的promise实例,则按照新实例的状态处理
  • 用await可以把一个异步任务变为类似于同步的效(本质不是同步,还是异步,而且是异步中的微任务)
 async function fn1 () {
      //console.log(a);
      return 10
      // return Promise.reject('10') //手动返回实例
    }
    console.log(fn1()); // 加上async 返回一个promise实例
     Promise {<fulfilled>: 10}
     [[Prototype]]: Promise
     [[PromiseState]]: "fulfilled"
     [[PromiseResult]]: 10
  function fn1 () {
      return 10
    }

    function fn2 () {
      return new Promise((resolve => {
        setTimeout(() => {
          resolve(2)
        }, 2000)
      }))
    }
    function fn3 () {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          reject(3)
        }, 1000)
      })
    }
 (async function () {
      // 方法中想用await,必须把方法基于async修饰
      // + 先把fn2执行,观察fn2返回的是成功还是失败的promise
      // + 异步性:它会把当前上下文,await 下面的代码整体都当作一个异步的微任务,放置在EventQueue中
      // + await 只是处理promise实例是成功状态的,如果返回状态是成功,则value拿到的是[[PromiseResult]],并且把之前存储的异步
      // 任务拿到栈中让主线程把其执行
      // await 处理后,立即把函数执行,哪怕函数立即返回成功或者失败的状态,await也没有把其立即处理,而是先等同步的都执行完,
      // 再去处理执行这些异步的任务
      let value = await fn2()
      console.log(value);
      console.log('@@');
    })()
    console.log(1);
(async function () {
      try {
        // 对于await 处理的时候,如果返回的是失败promise,则await下面代码不再执行,失败情况下没有处理浏览器抛出异常信息,
        // 此时我们可以基于 try/catch捕获异常,从而进行异常的处理
        let value = await fn3() 
        console.log(value);
        console.log('@@');
      } catch (e) { }

    })()