Promise

111 阅读8分钟

Promise概述

概念

Promise是异步编程的一种解决方案,可以替代传统的解决方案--回调函数和事件。ES6统一了用法,并原生提供了Promise对象。作为对象,Promise有以下两个特点:

特点

(1)对象的状态不受外界影响。

(2)一旦状态改变了就不会再变,也就是说任何时候Promise都只有一种状态。

状态

Promise的实例有三个状态 :

  1. Pending(进行中)
  2. Resolved(已完成)
  3. Rejected(已拒绝)

把一件事情交给promise时,它的状态就是Pending,任务完成了状态就变成了Resolved、没有完成失败了就变成了Rejected

常用方法

then

then中一般传入两个参数(函数),第一个对应resolve成功的回调,第二个对应reject失败的回调

then的第二个参数就等同于catch方法,但是需要注意

  • Promise内部报错,reject抛出错误后,then的第二个参数和catch方法都存在的情况下,只有then的第二个参数能捕获到,如果then的第二个参数不存在,catch方法会捕捉到
  • catch不仅捕捉promise中抛出的错误,还会捕捉前面then中的错误

catch

catch:捕捉promise错误函数,和then的第二个参数(函数)作用一样,处理错误,由于Promise抛出错误具有冒泡性质,能够不断传递,会传到catch中,所以一般来说所有错误处理放在catch中,then中只处理成功的,同时catch还会捕捉then中第一个参数(函数)抛出的异常

finally

finally 方法没有参数,也不会改变 Promise 的状态,它只是在 Promise 结束时提供了一个通知机制,让我们可以在 Promise 结束后执行一些清理工作(比如操作文件的时候关闭文件流)。

all

接受一个具有Iterable接口的类型,如数组,Map,Set,传入多个promise,
每一个promise执行成功resolve,最后才执行成功(返回一个Promise实例),进入then,否则失败进入catch

allSettled

allSettled:接受一个具有Iterable接口的类型,如数组,Map,Set,传入多个promise,
每个promise状态改变成fulfilled或者rejected之后返回,返回的是一个数组对象,对象中有状态status和每一项的返回结果value

race

race:以快为准,数组中所有的promise对象,有一个先执行了何种状态,该对象就为何种状态,并执行相应函数 接受一个具有Iterable接口的类型,如数组,Map,Set,传入多个promise, 其中有一个promise返回了,不管是fulfilled或者rejected,直接返回这个promise的结果

any

any: 接受一个具有Iterable接口的类型,如数组,Map,Set,传入多个promise,
当传入的任何一个promise成功的时候,不管其他是否成功或者失败,会把成功的那个promise返回

代码实现

const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'

function myPromise(fn){
	this.state = PENDING;
	this.value = null;
	this.resolveCallbacks = [];
	this.rejectedCallbacks = [];
	
	function resolve(value){
		this.state = RESOLVED;
		this.value = value;
		this.resolveCallbacks.map(cb => cb(this.value))
	}

	function reject(value){
		this.state = REJECTED;
		this.value = value;
		this.rejectedCallbacks.map(cb => cb(this.value))
	}
	
	try{
		fn(resolve,reject)
	}catch(e){
		reject(e)
	}

	myPromise.prototype.then = function(onFulfilled,onRejected){
		const that = this;
		onFulfilled = typeof onFulfilled === 'function' ? onFulfilled:v =>{}
		onRejected = typeof onRejected === 'function' ? onRejected:v =>{}
		if(that.state === PENDING){
			this.resolveCallbacks.push(onFulfilled)
			this.rejectedCallbacks.push(onRejected)
		}
		if(that.state === RESOLVED){
			onFulfilled(that.value)
		}
		if(that.state === REJECTED){
			onRejected(that.value)
		}
		return that;
	}
	
}

全部代码【含常用方法】


class MyPromise {
  static PENDING = "等待";
  static FULFILLED = "成功";
  static REJECTED = "失败";
  constructor(func) {
    this.status = MyPromise.PENDING; // 定义状态
    this.result = null; // 定义返回结果
    this.resloveCallbacks = []; // 存放reslove回调函数的数组
    this.rejectCallbacks = []; // 存放reject回调函数的数组
    // 当Promise中抛出错误时,会把promise的状态改为失败并且将错误设置为结果
    try {
      func(this.reslove.bind(this), this.reject.bind(this)); // 执行传入的函数
    } catch (err) {
      this.reject(err);
    }
  }
  reslove(result) {
    // resolve是在事件循环末尾执行的,所以这里用setTimeout包裹一下
    setTimeout(() => {
      this.status = MyPromise.FULFILLED; // 更改状态
      this.result = result;
      // 遍历执行PENDING状态时保存的reslove回调函数
      this.resloveCallbacks.forEach((callback) => {
        callback(result);
      });
    });
  }
  reject(result) {
    // reject同样用setTimeout包裹一下
    setTimeout(() => {
      this.status = MyPromise.FULFILLED; // 更改状态
      this.result = result;
      // 遍历执行PENDING状态时保存的reject回调函数
      this.rejectCallbacks.forEach((callback) => {
        callback(result);
      });
    });
  }
  then(onFULFILLED, onREJECTED) {
    let newPromise = new MyPromise((resolve, reject) => {
      // then中两个参数可以传入undefined,做下处理
      onFULFILLED =
        typeof onFULFILLED === "function"
          ? onFULFILLED
          : (value) => {
              value;
            };
      onREJECTED =
        typeof onREJECTED === "function"
          ? onREJECTED
          : (reason) => {
              throw reason;
            };
      if (this.status === MyPromise.PENDING) {
        // PENDING状态时
        this.resloveCallbacks.push(() => {
          try {
            let nextResult = onFULFILLED(this.result);
            resolvePromise(newPromise, nextResult, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
        this.rejectCallbacks.push(() => {
          try {
            let nextResult = onREJECTED(this.result);
            resolvePromise(newPromise, nextResult, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      }
      if (this.status === MyPromise.FULFILLED) {
        setTimeout(() => {
          try {
            // 执行成功后的回调
            let nextResult = onFULFILLED(this.result);
            resolvePromise(newPromise, nextResult, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      }
      if (this.status === MyPromise.REJECTED) {
        setTimeout(() => {
          try {
            // 执行失败后的回调
            let nextResult = onREJECTED(this.result);
            resolvePromise(newPromise, nextResult, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      }
    });
    return newPromise;
  }
  catch(onREJECTED) {
    return this.then(null, onREJECTED);
  }
  finally() {
    return this.then(undefined, undefined);
  }
  static resolve(value) {
    return new MyPromise((resolve) => {
      resolve(value);
    });
  }
  static reject(reason) {
    return new MyPromise((resolve, reject) => {
      reject(reason);
    });
  }
  static all(promises) {
    return new MyPromise((resolve, reject) => {
      // 创建一个空数组results,用于存储每个Promise的 resolve 结果
      const results = [];
      let count = 0;

      const processResult = (index, result) => {
        // 结果存入results数组(具有Iterator接口的对象)中,并更新count变量
        results[index] = result;
        count++;

        if (count === promises.length) {
          // 如果count等于Promise 数组(具有Iterator接口的对象)的长度,则说明所有Promise都resolve了,此时调用 resolve 方法
          resolve(results);
        }
      };
      // 遍历传入的Promise数组(具有 Iterator 接口的对象),对每个Promise调用then方法
      for (const [index, promise] of [...promises].entries()) {
        Promise.resolve(promise).then((result) => {
          processResult(index, result);
        }, reject);
      }
    });
  }
}

function resolvePromise(newPromise, x, resolve, reject) {
  if (x === newPromise) {
    // 因为x是回调的结果值,如果x指向newPromise即自己,那么会重新解析自己,导致循环调用
    throw new TypeError("禁止循环调用");
  }
  // 如果x是一个Promise,我们必须等它完成(失败或成功)后得到一个普通值时,才能继续执行。
  // 那我们把要执行的任务放在x.then()的成功回调和失败回调里面即可
  // 这就表示x完成后就会调用我们的代码。

  // 但是对于成功的情况,我们还需要再考虑下,x.then成功回调函数的参数,我们称为y
  // 那y也可能是一个thenable对象或者promise
  // 所以如果成功时,执行resolvePromise(promise2, y, resolve, reject)
  // 并且传入resolve, reject,当解析到普通值时就resolve出去,反之继续解析
  // 这样子用于保证最后resolve的结果一定是一个非promise类型的参数

  if (x instanceof MyPromise) {
    x.then(
      (y) => {
        resolvePromise(newPromise, y, resolve, reject);
      },
      (r) => reject(r)
    );
  }
  // (x instanceof myPromise) 处理了promise的情况,但是很多时候交互的promise可能不是原生的
  // 就像我们现在写的一个myPromise一样,这种有then方法的对象或函数我们称为thenable。
  // 因此我们需要处理thenable。
  else if ((typeof x === "object" || typeof x === "function") && x !== null) {
    // 暂存x这个对象或函数的then,x也可能没有then,那then就会得到一个undefined
    try {
      var then = x.then;
    } catch (e) {
      // 如果读取then的过程中出现异常则reject异常出去
      return reject(e);
    }
    // 判断then是否函数且存在,如果函数且存在那这个就是合理的thenable,我们要尝试去解析
    if (typeof then === "function") {
      // 状态只能更新一次使用一个called防止反复调用
      // 因为成功和失败的回调只能执行其中之一
      let called = false;
      try {
        then.call(
          x,
          (y) => {
            // called就是用于防止成功和失败被同时执行,因为这个是thenable,不是promise
            // 需要做限制如果newPromise已经成功或失败了,则不会再处理了
            if (called) return;
            called = true;
            resolvePromise(newPromise, y, resolve, reject);
          },
          (r) => {
            // called就是用于防止成功和失败被同时执行,因为这个是thenable,不是promise
            // 需要做限制如果newPromise已经成功或失败了,则不会再处理了
            if (called) return;
            called = true;
            reject(r);
          }
        );
        // 上面那一步等价于,即这里把thenable当作类似于promise的对象去执行then操作
        // x.then(
        //   (y) => {
        //     if (called) return;
        //     called = true;
        //     resolvePromise(newPromise, y, resolve, reject);
        //   },
        //   (r) => {
        //     if (called) return;
        //     called = true;
        //     reject(r);
        //   }
        // )
      } catch (e) {
        // called就是用于防止成功和失败被同时执行,因为这个是thenable,不是promise
        // 需要做限制如果newPromise已经成功或失败了,则不会再处理了
        if (called) return;
        called = true;
        reject(e);
      }
    } else {
      // 如果是对象或函数但不是thenable(即没有正确的then属性)
      // 当成普通值则直接resolve出去
      resolve(x);
    }
  } else {
    return resolve(x);
  }
}

优缺点

缺点

  1. 顺序错误处理:Promise链中的错误很容易被无意中默默忽略掉。
  2. 单一值:Promise只能有一个完成值或一个拒绝理由
  3. 单决议:Promise只能被决议一次
  4. 惯性:运动状态(使用回调)的代码库会一直保持运动状态(使用回调),直到受到一位聪明的,理解Promise开发者的作用。
  5. 无法取消的Promise:一旦创建,如果出现了某一种情况使得这个任务悬而未决的话,你也没办法从外部停止他的进程。
  6. Promise性能:稍慢一点,但是作为交换,你得到的是大量内建的可信任性,对Zalgo的避免以及可组合性。

一些特殊情况

值穿透

.then .catch 期望是函数, 如果传入非函数则会发生值穿透

promise的data 就会保存上一个promise.data

Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log) // 1

链式调用-不同返回值

如果返回的是普通对象,将作为下次then函数的参数传入;
如果返回的是Promise对象(p2),那么p2将会接管以后的Promise链

Promise 链的返回值

错误处理

对于多数开发者来说,错误处理最自然地形式就是同步的try…catch结构。遗憾的是,他只能是同步的,无法用于异步代码模式。

function foo(){
    setTimeout(function(){
            baz.bar();
    },100)
}
try{
   foo() //后面的baz.bar()会抛出全局错误
}
catch{
   //永远不会到达这里
}

在回调中,一些模式化的错误处理方式已经出现,最值得一提的是error-first回调风格:

function foo(cb){
    setTimeout(function(){
        try{
                var x = baz.bar();
                cb(null,x);//成功
        }
        catch(err){
                cb(err)
        }
    },100)
}

foo(function(err,val){
    if(err){
            console.error(err);
    }else{
            console.log(val);
    }
})

只有在baz.bar()调用同步地立即执行的情况下才有用,否则如果baz.bar()本身有自己的异步完成函数,其中的任何错误都不能被捕捉。