阅读 525

Promise全攻略(三.Promise的标准规范)

Promise是异步任务同步化的解决方案?本文讲通过以下几方面来介绍Promise:

  1. Promise的出现为了解决什么问题
  2. Promise内部对任务的处理
  3. Promise的标准规范
  4. Promise的方法使用
  5. Promise在eventLoop中执行顺序
  6. 源码地址

Promise知识分五个章节来阐述,这篇我们讨论:Promise的标准规范
Promise的规范有很多,比如Promise/A,Promise/B,Promise/D,Promise/A升级版,Promise/A+。ES6中采用了Promise/A+规范。
本节可能描述抽象,不涉及到应用层面,但搞懂后会对你使用Promise有很大提升。

其他规范了解

简单的说说Promise/A/B/D的规范吧,他们都是由CommonJS组织提出的,最早提出Promise/A,与现在用的Promise类似,Promise/B时在A基础上定义了一组Promise模块需要实现的API(when(),asap(),enquene()...),Promises/D 规范添加如何判断一个对象是Peomise,对B定义的ref,reject,def,defer方法做了进一步约束。 我们真正要了解的是Promise/A+规范,是Promise/A+组织发布的规范,以Promise/A为基础进行补充修订。

术语

Promise 是一个包含了兼容promise规范then方法的对象或函数。
thenable 是一个包含了then方法的对象或函数。
value 是任意js值。
exception 是由throw表达式抛出来的值。
reason 是一个用于描述Promise被拒绝原因的值。

Promise规范要求

  • Promise有三种状态:pending 、fulfilledrejected
  • Promise含有then方法。
  • Promise含有Promise Resolution Procedure(Promise的状态解析方法)。
  • 其他Promise中挂载的方法返回值

Promise状态

Promise必须处于pending,fulfilled,rejected三种状态其中一种。

  1. 我们实例化一个Promise时,传入一个函数作为参数,此参数称之为executor(执行函数),他可以告诉我们何时将Promise状态从pending转换成其余两种形态中的一种。\
  2. then()是被实例化出的一个方法,他能传入两个函数作为参数,作为fulfilledrejected两个状态,当Promise从pending转化成其他状态时会触发对应回调,并且它不能再转换成其他状态。
let p = new Promise((resolve, reject)=>{  //为executor函数
    let x = Math.random();
    if(x > 0.5) resolve('由pending变为fulfilled');
    reject('由pending变为rejected')
})
p.then(value =>{},reason=>{}) //绑定成功后的回调函数
复制代码

.then方法

再详细说说.then方法:

1.它接收两个参数。
promise.then(onFulfilled, onRejected)
复制代码

onFulfilledonRejected都是可选参数,一般为函数。如果onFulfilledonRejected不是一个函数,必须被忽略。
onFulfilled为函数时:

  • 它必须是Promise被解决后才调用,Promise中resolve传的值作为他第一个参数
  • 它一定不能被调用多次

onRejected为函数时:

  • 它必须是Promise被拒绝后才调用,Promise中reject传的值作为他第一个参数
  • 它一定不能被调用多次

2.同一个Promise上的then可能被调用多次:
如果promise被解决,所有相应的onFulfilled回调必须按照他们原始调用then的顺序执行。
如果promise被拒绝,所有相应的onRejectd回调必须按照他们原始调用then的顺序执行。
举例好说明

//第一题
Promise.resolve().then(()=>{
    console.log(1) //1  resolved
}).then(val=>{
    console.log(2) // 2 resolved
},err=>{
    console.log(3) 
})

//返回resolved状态 log结果:1,2 

//第二题
Promise.resolve().then(val=>{
    console.log(1) //1 
    throw  new Error("error1") 
}).then(val=>{
    console.log(2) 
},err=>{
    console.log(3) 
}).then(val=>{
    console.log(4)
    throw  new Error("error2")
},err=>{
    console.log(5) 
}).then(val =>{
    console.log(6) 
},err=>{
    console.log(7) 
})

//返回resolved状态 log结果:1,3,4,7

复制代码

从例子我们可以看出,无论是解决和拒绝,都要继续执行.then方法,下次的.then根据上次.then执行的结果,执行不用的回调。同时这也体现出.then方法的另一个特性:
3.then方法必会返回一个新的Promise。
注意:在多次调用.then方法,会出现值的穿透,第一个then的参数会穿透到第二个then方法。

let p = new Promise((resolve,reject)=>{
  resolve('resolve');
});
p.then().then((value)=>{
  console.log(value); //会输出resolve
});
复制代码

Promise Resolution Procedure(Promise的状态解析方法)

Promise状态解析方法的作用是将then时返回的promise2的状态改变并赋予其vlaue/reason。 在网上找了很多资料,都是文字描述,很晦涩难懂,下面我用文字加代码的形式,带大家了解什么是状态解析:
下面参数说的是 Promise.resolve(x) 中的x。
一共四种情况:
1.如果参数是Promise实例本身,则抛出错误。

let x = Promise.resolve(x)
x.then(
    (val) => { console.log('onFilfelled', val); }, 
    (err) => { console.log('onRejected', err); }
 )
//Uncaught ReferenceError: Cannot access 'x' before initialization
复制代码

2.如果参数是一个Promise对象,则then函数的执行取决于这个参数的状态,如果参数也调用了resolve(y),其中y也是一个promise对象then函数的执行取决于这个promise对象,以此类推。

let p = new Promise((resolve, reject) => {
    reject('fail')
})
let x = Promise.resolve(p)
x.then(
    (val) => { console.log('onFilfelled', val); }, 
    (err) => { console.log('onRejected', err); }
 )
//onRejected fail
复制代码

3.如果参数是一个thenable对象,就是一个对象包含then这个属性,或者是一个函数包含一个then的静态方法,那么直接执行then函数。

let a = {
    then: () => { console.log(123); }
}
let x = Promise.resolve(a)
console.log(321);
// 321 123
复制代码

这里先存疑,为什么会先执行321 再执行123 了解当带.then方法的变量传递给 Promise.resolve() 会自动执行.then方法。
4.如果参数是一个普通值,直接变成onFullfilled状态,然后执行后面的then函数。

let x = Promise.resolve(10)
x.then(
    (val) => { console.log('onFilfelled', val); }, 
    (err) => { console.log('onRejected', err); }
 )
//onFilfelled 10
复制代码

这里就是状态解析方法对状态判断的机制,接下来说一下为什么先打印321,.then()是个异步任务,会在下一个tick再执行,但如果是一下这种情况

new Promise((resolve, reject) => {
  let a = new Promise(resolve => {
    resolve(10)
  })
  let b = new Promise(resolve => {
    let c = Promise.resolve(10)
    resolve(c)
  })
  console.log(a)
  console.log(b)
  Promise.resolve(0)
    .then(() => {
      console.log('micro1')
      console.log(b)
    })
    .then(() => {
      console.log('micro2')
      console.log(b)
    })
})
// setTimeout(() => {
//     console.log('marco')
//     console.log(b)
// })
//结果
Promise {<resolved>: 10}
Promise {<pending>}
4
micro1
Promise {<resolved>: 10}
Promise {<pending>}
micro2
Promise {<resolved>: 10}
复制代码

我们发现,a直接输出成功状态10,而b在第一个then中输出结果为b中定义的Promise c,所以为执行状态pending。但第二个then中b直接输出resolved状态的10了,这里我们把状态的改变过程称之为状态同步
对eventLoop熟悉的朋友可能会知道,当第一个tick执行完毕,进入第二个tick时,Promise的状态同步会放在官方称呼PromiseResolveThenableJob中执行,而PromiseResolveThenableJob会放在PromiseJobsjob queue执行,而job queue是microTask的别名,也就是说第二次tick执行的微任务其实会把b的pending状态改为resolved状态。大家也可以复制下代码,把.then的回调删除,直接使用宏任务setTimeout进入下一个循环中对比。也可看到第一次状态为pending,在setTimeout中的为resolved


如果此文章对您有帮助或启发,那便是我的荣幸

文章分类
前端
文章标签