Promise对象

303 阅读12分钟

1. Promise含义

所谓Promise,简单来说就是一个容器,里面保存着某个未来的才会结束的事件(通常是一个异步操作的结果。从语法上说,Promise是一个对象,从它可以异步获取操作的消息。

1.1 Promise两个特点

  1. 对象状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfillde(已成功)、rejected(已失败)。
  2. 状态一旦改变,就不会在改变。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。

1.2 Promise优点

  1. Promise对象,可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。
  2. Promise对象提供统一的接口,使得控制异步操作更加容易。

1.3 Promise缺点

  1. 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
  2. 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
  3. 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

2. promise基本用法

2.1 resolve和reject

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。

reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

2.2 race

Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);

上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

2.3 all

Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例

const p = Promise.all([p1, p2, p3]);

上面代码中,Promise.all()方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。

p的状态由p1、p2、p3决定,分成两种情况。

  1. 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

  2. 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

2.4 allSettled

Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。

2.5 any

该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。

3. EventLoop

EventLoop是一个程序结构,用于等待和发送消息和事件。简单说,就是在程序中设置两个线程:一个负责程序本身的运行,称为"主线程";另一个负责主线程与其他进程(主要是各种I/O操作)的通信,被称为"Event Loop线程"(可以译为"消息线程")。

大体解释一下NodeJS的Event Loop过程:

  1. 执行全局Script的同步代码
  2. 执行microtask微任务,先执行所有Next Tick Queue中的所有任务,再执行Other Microtask Queue中的所有任务
  3. 开始执行macrotask宏任务,共6个阶段,从第1个阶段开始执行相应每一个阶段macrotask中的所有任务,注意,这里是所有每个阶段宏任务队列的所有任务,在浏览器的Event Loop中是只取宏队列的第一个任务出来执行,每一个阶段的macrotask任务执行完毕后,开始执行微任务,也就是步骤2

3.1 宏队列

宏队列,macrotask,也叫tasks。 一些异步任务的回调会依次进入macro task queue,等待后续被调用,这些异步任务包括:

  1. setTimeout
  2. setInterval
  3. setImmediate (Node独有)
  4. requestAnimationFrame (浏览器独有)
  5. I/O
  6. UI rendering (浏览器独有)

NodeJS的Event Loop中,执行宏队列的回调任务有6个阶段,如下图:

image.png

各个阶段执行的任务如下:

  1. timers阶段:这个阶段执行setTimeout和setInterval预定的callback
  2. I/O callback阶段:执行除了close事件的callbacks、被timers设定的callbacks、setImmediate()设定的callbacks这些之外的callbacks
  3. idle, prepare阶段:仅node内部使用
  4. poll阶段:获取新的I/O事件,适当的条件下node将阻塞在这里
  5. check阶段:执行setImmediate()设定的callbacks
  6. close callbacks阶段:执行socket.on('close', ....)这些callbacks

NodeJS中宏队列主要有4个

  1. Timers Queue
  2. IO Callbacks Queue
  3. Check Queue
  4. Close Callbacks Queue 这4个都属于宏队列,但是在浏览器中,可以认为只有一个宏队列,所有的macrotask都会被加到这一个宏队列中,但是在NodeJS中,不同的macrotask会被放置在不同的宏队列中。

3.2 微队列

微队列,microtask,也叫jobs。 另一些异步任务的回调会依次进入micro task queue,等待后续被调用,这些异步任务包括:

  1. process.nextTick (Node独有)
  2. Promise
  3. Object.observe
  4. MutationObserver

NodeJS中微队列主要有2个:

  1. Next Tick Queue:是放置process.nextTick(callback)的回调任务的
  2. Other Micro Queue:放置其他microtask,比如Promise等 在浏览器中,也可以认为只有一个微队列,所有的microtask都会被加到这一个微队列中,但是在NodeJS中,不同的microtask会被放置在不同的微队列中。

4.Promise A+规范

4.1 Promise的声明

class声明,由于 new Promise((resolve,reject) =>{}) , 所以传入一个参数(函数) ,PromiseA+里叫他executor(作业执行器),传入就执行。

class Promise {
    // 构造器
    constructor(executor) {
        // 成功
        let resolve = () => {};
        // 失败
        let reject = () => {};
        // 立即执行
        executor(resolve, reject);
    }
}

4.2 解决基本状态

PromiseA+对Promise有规定:

new Promise((resolve, reject)=>{resolve(value)})
resolve为成功,接收参数value,状态改变为fulfilled,不可再次改变。

new Promise((resolve, reject)=>{reject(reason)})
reject为失败,接收参数reason,状态改变为rejected,不可再次改变。 若是executor函数报错 直接执行reject();

于是我们获得以下代码

class Promise {
    // 构造器 
    constructor(executor) {
        // 初始化state为等待态
        this.state = 'pending'
        // 成功的值
        this.value = undefined
        // 失败的原因
        this.reason = undefined

        // 成功
        let resolve = value => {
            // state改变,resolve调用就会失败
            if (this.state === 'pending') {
                // resolve调用后,state转化为成功态
                this.state = 'fulfilled'
                // 储存成功的值
                this.value = value
            }
        }
        // 失败
        let reject = reason => {
            // state改变,reject调用就会失败
            if (this.state === 'pending') {
                // reject调用后,state转化为失败态
                this.state = 'rejected'
                // 储存失败的原因
                this.reason = reason
            }
        }

        // 如果executor执行报错,直接执行reject
        try {
            executor(resolve, reject)
        } catch (err) {
            reject(err)
        }
    }
}

4.3 then方法

PromiseA+规定:Promise有一个叫做then的方法,里面有两个参数:onFulfilled,onRejected,成功有成功的值,失败有失败的原因
当状态state为fulfilled,则执行onFulfilled,传入this.value。当状态state为rejected,则执行onRejected,传入this.reason
onFulfilled,onRejected如果他们是函数,则必须分别在fulfilled,rejected后被调用,value或reason依分别为他们的第一个参数

class Promise {
    // 构造器 
    constructor(executor) {...}

    // then方法 有两个参数 onFulfilled onRejected
    then(onFulfilled, onRejected) {
        // 状态为fulfilled,执行onFulfilled,传入成功的值
        if (this.state === 'fulfilled') {
            onFulfilled(this.value)
        }
        if (this.state === 'rejected') {
            onRejected(this.reason)
        }
    }
}

4.4 解决异步实现

当resolve在setTimeout内执行,then时state还是pending等待状态 我们就需要在then调用的时候,将成功和失败存到各自的数组,一旦reject或者resolve,就调用它们
类似于发布订阅,先将then里面的两个函数储存起来,由于一个Promise可以有多个then,所以存在同一个数组内。

//executor 执行器的实现
//状态转移
//thenable 
//如何让执行器决定状态转移的执行
class Promise{
    constructor(executor){
        this.state = 'pending' //初始化未完成状态
        //成功的值
        this.value = undefined;
        //失败的原因
        this.reason = undefined;
        //异步任务会把结果交给resolve
        this.onResolvedCallbacks = [];
        this.onRejectedCallbacks = []

        let resolve = (value) =>{
            // console.log("fullfilled状态被执行")
            if(this.state == "pending"){
                this.value = value
                this.state = "fulfilled"
                this.onResolvedCallbacks.forEach(fn =>fn())
            }   
            console.log("fulfilled状态被执行",this.value,this.state)
            //onFulfilled执行一下 记忆功能
     
        }

        let reject = (reason) =>{
            // console.log("reject状态被执行")
            if(this.state =="pending"){
                this.reason = reason
                this.state = "rejected"
                this.onRejectedCallbacks.forEach(fn =>fn())
            }
            console.log("reject状态被执行",this.reason,this.state)
        }
        // executor(resolve,reject)
        try{
            executor(resolve,reject)
        }catch(e){
            reject(err)
        }
        
    }
    //当前promise 解决了 完成了状态转移 把控制权交出来

    then(onFulfilled,onRejected){
        console.log('then ........')
        //如果状态为fulfilled 时 , 传入成功后的回调 将执行权转移
        if(this.state == 'fulfilled'){
            console.log('onFulfilled,----')
            onFulfilled(this.value);
        }
        //状态为rejected 传入为失败后的回调
        if(this.state == 'rejected'){
            onRejected(this.reason)

        }

        if(this.state == 'pending'){
            this.onResolvedCallbacks.push(()=>{
                onFulfilled(this.value)
            })
            this.onRejectedCallbacks.push(()=>{
                onRejected(this.reason)
            })
        }
    }
    // then(){

    // }
}
new Promise((resolve, reject) => {
    // 将花时间比较多的任务封装起来, 以实现异步变同步 
    setTimeout(() => {
      console.log(0)
      resolve(10);
    // throw new Error('出错了')
    }, 1000)
  }).then((data)=>{
    console.log(data,"++++++");
  })

4.5解决链式调用

PromiseA+规定了一种方法,就是在then里面返回一个新的promise,称为promise2: promise2 = new Promise((resolve, reject)=>{})
将这个promise2返回的值传递到下一个then中
如果返回一个普通的值,则将普通的值传递给下一个then中

PromiseA+则规定onFulfilled()或onRejected()的值,即第一个then返回的值,叫做x,判断x的函数叫做resolvePromise

// MyPromise.js

// 先定义三个常量表示状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

// 新建 MyPromise 类
class MyPromise {
  constructor(executor){
    // executor 是一个执行器,进入会立即执行
    // 并传入resolve和reject方法
    executor(this.resolve, this.reject)
  }

  // 储存状态的变量,初始值是 pending
  status = PENDING;

  // resolve和reject为什么要用箭头函数?
  // 如果直接调用的话,普通函数this指向的是window或者undefined
  // 用箭头函数就可以让this指向当前实例对象
  // 成功之后的值
  value = null;
  // 失败之后的原因
  reason = null;
  onFulfilledCallbacks = [];
// 存储失败回调函数
// onRejectedCallback = null;
onRejectedCallbacks = [];
  // 更改成功后的状态
 // MyPromise.js

// 更改成功后的状态
resolve = (value) => {
    // 只有状态是等待,才执行状态修改
    if (this.status === PENDING) {
      // 状态修改为成功
      this.status = FULFILLED;
      // 保存成功之后的值
      this.value = value;
      // ==== 新增 ====
      // resolve里面将所有成功的回调拿出来执行
    //   console.log(this.onFulfilledCallbacks)
      while (this.onFulfilledCallbacks.length) {
        // Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空
        this.onFulfilledCallbacks.shift()(value)
      }
    }
  }
  
// MyPromise.js
// 更改失败后的状态
// MyPromise.js

// 更改失败后的状态
reject = (reason) => {
    // 只有状态是等待,才执行状态修改
    if (this.status === PENDING) {
      // 状态成功为失败
      this.status = REJECTED;
      // 保存失败后的原因
      this.reason = reason;
      // ==== 新增 ====
      // resolve里面将所有失败的回调拿出来执行
      while (this.onRejectedCallbacks.length) {
        this.onRejectedCallbacks.shift()(reason)
      }
    }
  }  
  
  then(onFulfilled, onRejected) {
    // ==== 新增 ====
    // 为了链式调用这里直接创建一个 MyPromise,并在后面 return 出去
    const promise2 = new MyPromise((resolve, reject) => {
      // 这里的内容在执行器中,会立即执行
      if (this.status === FULFILLED) {
        // 获取成功回调函数的执行结果
        const x = onFulfilled(this.value);
        // 传入 resolvePromise 集中处理
        resolvePromise(x, resolve, reject);
      } else if (this.status === REJECTED) {
        onRejected(this.reason);
      } else if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(onFulfilled);
        this.onRejectedCallbacks.push(onRejected);     
      }
    }) 
    return promise2;
  }
}

function resolvePromise(x, resolve, reject) {
  // 判断x是不是 MyPromise 实例对象
  if(x instanceof MyPromise) {
    // 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
    // x.then(value => resolve(value), reason => reject(reason))
    // 简化之后
    x.then(resolve, reject)
  } else{
    // 普通值
    resolve(x)
   
  }
}
// test.js

const promise = new MyPromise((resolve, reject) => {
  // 目前这里只处理同步的问题
  resolve('success')
})

function other () {
  return new MyPromise((resolve, reject) =>{
    resolve('other')
  })
}
promise.then(value => {
    console.log(1)
  console.log('resolve', value)
  return other()
}).then(value => {
  console.log(2)
  console.log('resolve', value)
})

  

5. 回调地狱

5.1 什么是回调地狱

  1. 多层嵌套问题
  2. 每种任务的处理结果存在两种可能性(成功或失败) , 那么需要在每种任务执行结束后分别处理这两种可能性

5.2 解决方法

Promise利用三大技术来解决回调地狱

5.2.1 回调函数延迟绑定

通过后面的 then 方法传入的,即延迟传入。这就是回调函数延迟绑定。

let readFilePromise = (filename) => {
  fs.readFile(filename, (err, data) => {
    if(err) {
      reject(err);
    }else {
      resolve(data);
    }
  })
}
readFilePromise('1.json').then(data => {
  return readFilePromise('2.json')
});

5.2.2 返回值穿透

我们会根据 then 中回调函数的传入值创建不同类型的Promise, 然后把返回的 Promise 穿透到外层, 以供后续的调用。这里的 x 指的就是内部返回的 Promise,然后在 x 后面可以依次完成链式调用。这便是返回值穿透的效果。

let x = readFilePromise('1.json').then(data => {
  return readFilePromise('2.json')//这是返回的Promise
});
x.then(/* 内部逻辑省略 */)

5.2.3 错误冒泡

这样前面产生的错误会一直向后传递,被 catch 接收到,就不用频繁地检查错误了。

readFilePromise('1.json').then(data => {
    return readFilePromise('2.json');
}).then(data => {
    return readFilePromise('3.json');
}).then(data => {
    return readFilePromise('4.json');
}).catch(err => {
  // xxx
})

5.3 解决效果

  1. 实现链式调用,解决多层嵌套问题
  2. 实现错误冒泡后一站式处理,解决每次任务中判断错误、增加代码混乱度

6. then方法

6.1 then方法识别调用promise

如果 then 方法返回的是自己的 Promise 对象,则会发生循环调用,这个时候程序会报错

// test.js

const promise = new Promise((resolve, reject) => {
  resolve(100)
})
const p1 = promise.then(value => {
  console.log(value)
  return p1
})

6.2 捕获错误

6.2.1 捕获执行器错误

constructor(executor){
  // ==== 新增 ====
  // executor 是一个执行器,进入会立即执行
  // 并传入resolve和reject方法
  try {
    executor(this.resolve, this.reject)
  } catch (error) {
    // 如果有错误,就直接执行 reject
    this.reject(error)
  }
}

6.2.2 then执行时错误捕获


then(onFulfilled, onRejected) {
  // 为了链式调用这里直接创建一个 MyPromise,并在后面 return 出去
  const promise2 = new MyPromise((resolve, reject) => {
    // 判断状态
    if (this.status === FULFILLED) {
      // 创建一个微任务等待 promise2 完成初始化
      queueMicrotask(() => {
        // ==== 新增 ====
        try {
          // 获取成功回调函数的执行结果
          const x = onFulfilled(this.value);
          // 传入 resolvePromise 集中处理
          resolvePromise(promise2, x, resolve, reject);
        } catch (error) {
          reject(error)
        }  
      })  
    } else if (this.status === REJECTED) {
      // 调用失败回调,并且把原因返回
      onRejected(this.reason);
    } else if (this.status === PENDING) {
      // 等待
      // 因为不知道后面状态的变化情况,所以将成功回调和失败回调存储起来
      // 等到执行成功失败函数的时候再传递
      this.onFulfilledCallbacks.push(onFulfilled);
      this.onRejectedCallbacks.push(onRejected);
    }
  }) 
  
  return promise2;
}

6.3 then参数可传可不传

我们处理 then 方法的时候都是默认传入 onFulfilled、onRejected 两个回调函数,但是实际上原生 Promise 是可以选择参数的单传或者不传,都不会影响执行。

then(onFulfilled, onRejected) {
  // 如果不传,就使用默认函数
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
  onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason};
}


参考链接:
blog.csdn.net/qq_43540219…
http://47.98.159.95/my_blog/blogs/javascript/js-async/004.html
juejin.cn/post/694531…
zhuanlan.zhihu.com/p/55511602