JavaScript系列 - Promise

105 阅读6分钟

JavaScript系列 - Promise

不止一次的学习Promise, 真正学懂Promise是每次遇到问题之后,反反复复的查阅资料,学习就是如此,重复之

是什么?

  • 不多解释,是什么的问题,随处可查,解决什么问题,想必前端人员再了解不过了。

理念

  • 观察者模式
const P = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve('result');
    }, 1000);
})
P.then(res => console.log, err => console.log(err));

我们来分析Promise的调用流程:

  • Promise的构造方法接受一个executor() , 在new Promise() 的时候立刻执行这个回调;
  • executor() 内部的异步任务被放入宏/微任务队列,等待执行;
  • then() 被执行,收集成功/失败回调,放入成功/失败队列;
  • executor() 的异步任务被执行,触发resolve/reject,从成功/失败队列中取出回调依次执行; 这就是一个典型的观察者模式,这种收集依赖 -> 触发通知 -> 取出执行依赖的方式,就是观察者模式的实现,在Promise中,执行顺序是:then收集依赖 -> 异步触发resolve -> resolve执行依赖。 我们知道了这一点之后,就能在脑海中勾勒出平时工作中写的业务代码,假如我们封装了一个用于公共请求的fetch方法,如下:
import fetch from './utils';
​
fetch('/abc/def', get).then(res => {
    console.log(res);
    setState({  // react中的setState
        data: res; 
    })
})

上面的代码在业务中及其常见,我们来解读一下:

  • fetch方法的参数为请求数据的url,以及请求方式,这个方法内部使用axios实现,也就是利用Promise来实现异步任务
  • 上面我们说到,then方法收集依赖,我们可以想到,这个方法是在fetch请求完毕才会调用的,这样,Promise就完成了它的任务,异步任务就这样很优雅的被实现。

实现

其实,如果不知道Promise中是如何实现这种异步回调的话,理解起来还是蛮费劲,只是知道了字面意思,那么我们先实现一个简单的Promise来大概了解一下

class MyPromise {
  // 构造函数接收一个方法
  constructor(executor) {
    this._resolveQueue = []; // then方法收集成功回调队列
    this._rejectQueue = []; // then方法收集的失败回调队列
​
    // 箭头函数可以避免this指向为undefined的问题
    let _resolve = (val) => {
      while (this._resolveQueue.length) {
        // 从成功的队列中取出回调依次执行
        const callback = this._resolveQueue.shift();
        callback(val);
      }
    };
    // 同上
    let _reject = (val) => {
      while (this._rejectQueue.length) {
        const callback = this._rejectQueue.shift();
        callback(val);
      }
    };
​
    // executor函数立即执行,并传入_resolve, _reject方法
    executor(_resolve, _reject);
  }
​
  // then方法收集成功或者失败方法 push进队列;
  then(resolveFn, rejectFn) {
    this._resolveQueue.push(resolveFn);
    this._rejectQueue.push(rejectFn);
  }
}

我们使用观察者模式简单的实现了一个Promise,使我们能够在then方法的回调里取得异步操作的返回值

Promise A+ 规范

  • 两条核心规则
  1. Promise本质是一个状态机,且状态只能为以下三种:Pending(等待态)Fulfilled(执行态)Rejected(拒绝态),状态的变更是单向的,只能从 Pending -> Fulfilled 或者 Pending -> Rejected
  2. then方法接收两个可选参数,分别对应状态改变时触发的回调。then方法返回一个Promise, then方法可以被同一个promise调用多次 我们补充Promise规范如下:
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class MyPromise {
  // 构造函数接收一个方法
  constructor(executor) {
    this.state = PENDING; // Promise初始状态
    this._resolveQueue = []; // then方法收集成功回调队列
    this._rejectQueue = []; // then方法收集的失败回调队列
​
    // 箭头函数可以避免this指向为undefined的问题
    let _resolve = (val) => {
      if (this.state !== PENDING) return; // 如果状态不是pending, 那么return;
      this.state = FULFILLED; // 变更状态
      while (this._resolveQueue.length) {
        // 从成功的队列中取出回调依次执行
        const callback = this._resolveQueue.shift();
        callback(val);
      }
    };
    // 同上
    let _reject = (val) => {
      if (this.state !== PENDING) return; // 如果状态不是pending, 那么return;
      this.state = REJECTED; // 变更状态
      while (this._rejectQueue.length) {
        const callback = this._rejectQueue.shift();
        callback(val);
      }
    };
​
    // executor函数立即执行,并传入_resolve, _reject方法
    executor(_resolve, _reject);
  }
​
  // then方法收集成功或者失败方法 push进队列;
  then(resolveFn, rejectFn) {
    this._resolveQueue.push(resolveFn);
    this._rejectQueue.push(rejectFn);
  }
}

then的链式调用

如何实现链式调用:

  1. then方法需要返回一个Promise,这样才能找到then方法,所以我们会把then方法的返回值包装成Promise;
  2. then的回调需要拿到上一个then的返回值;
  3. then的回调需要顺序执行,我们要等待当前Promise状态变更后,再执行下一个then收集的回调,这就要求我们对then的返回值进行分类处理
  then(resolveFn, rejectFn) {
    // 为了实现then方法的链式调用返回一个Promise
    return new MyPromise((resolve, reject) => {
      const fulfilledFn = (value) => {
        try {
          // 执行当前的Promise成功回调,并获取返回值, // 等于外部的resolve值, 或者then方法的return 值
          // 这个方法以前是在_resolveQueue队列中执行,现在提前执行,就是为了知道其返回值;
          let x = resolveFn(value);
          x instanceof MyPromise ? x.then(resolve, reject) : resolve(x);
        } catch (error) {
          reject(error);
        }
      };
      this._resolveQueue.push(fulfilledFn);
​
      const rejectedFn = (error) => {
        try {
          let x = rejectFn(error);
          x instanceof MyPromise ? x.then(resolve, reject) : reject(x);
        } catch (error) {
          reject(error);
        }
      };
​
      this._rejectQueue.push(rejectedFn);
    });
  }

Promise.prototype.catch()

catch()方法返回一个Promise, 并且处理拒绝的情况,它的行为与调用Promise.prototype.then(undefined, onRejected相同). // catch方法其实就是执行一下then的第二个回调

    catch(rejectFn) {
 return this.then(undefined, rejectFn);
}

Promise.prototype.finally()

finally() 方法返回一个Promise, 在promise结束时,无论结果是成功或者失败,都会执行指定的回调函数。在finally之后,还可以继续then. 并且会将值原封不动的传递给后面的then

    finally(callback) {
    return this.then(
        value => MyPromise.resolve(callback()).then(() => value),
        reason => MyPromise.resolve(callbacl()).then(() => {
        throw reason
        })
    )
}

Promise.resolve()

Promise.resolve(value)方法返回一个以给定值解析后的Promise对象。如果该值为promise, 返回这个promise; 如果这个值是thenable(即带有then方法),返回的promise会跟随这个thenable的对象,采用它的最终状态,否则返回的promise将以此值完成。此函数将类promise对象的多层嵌套展平。

static resolve(value) {
if (value instanceof MyPromise) return value;
return new Promise(resolve => resolve(value));
}

Promise.reject()

Promise.reject() 方法返回一个带有拒绝原因的Promise对象

static reject(reason) {
return new MyPromise((resolve, reject) => reject(reason))
}

Promise.all()

Promise.add(iterable) 方法返回一个Promise实例,此实例在iterable参数内所有的promise都完成或参数中不包含promise时回调完成,如果参数中promise有一个失败,此实例回调失败,失败原因是第一个失败promise的结果

static all(promiseArr) {
let index = 0;
let result = [];
return new MyPromise((resolve,reject) => {
promiseArr.forEach((p,i) => {
		MyPromise.resolve(p).then(
			val => {
				index++;
				result[i] = val;
				if (index === promiseArr.length) {
				resolve(result)
				}
			},
			err => {
			 reject(err);
			}
		)
	})
})
}

Promise.race()

Promise.race(iterable) 方法返回一个promise, 一旦迭代器中的某个promise解决或者拒绝,返回的promise就会解决或者拒绝

static race(promiseArr) {
return new MyPromise((resolve,reject) => 	{
// 同时执行Promise, 如果有一个Promise的状态发生改变,就更新Promise的状态
	for (let p of promiseArr) {
		MyPromise.resolve(p).then(
			value => {
				resolve(value);
			},
			err => {
			reject(err);
			}
		)
	}
})
}

\