louzhedong / blog

前端基础,深入以及算法数据结构
934 stars 84 forks source link

Promise原理及实现 #180

Open louzhedong opened 4 years ago

louzhedong commented 4 years ago

什么是Promise

Promise是抽象异步处理对象以及对其进行各种操作的组件,本质上其实就是一个状态机,在内部有两个数组分别保存着成功和失败的回调,来实现异步,其实是使用了设计模式中的观察者模式。

通过new Promise实例化的promise对象有以下三个状态

当Promise对象的状态从Pending转换为Fulfilled或Rejected之后,这个Promise对象的状态就不会再发生任何变化

Promise的方法

Promise.resolve

一般情况下都使用new Promise来创建对象,但我们也可以使用Promise.resolve(value)的快捷方式。

Promise.resolve(42)可以看做是以下代码的语法糖

new Promise(function(resolve) {
  resolve(42);
})

而且它返回的值也是一个promise对象,可以继续调用then方法

Promise.reject

类似Promise.resolve

Promise#then

不仅仅是一个回调函数,它还会将回调函数的返回值进行变换,创建并返回一个Promise对象

使用promise.then(onFulfilled, onRejected)的话

在promise.then(onFulfilled).catch(onRejected)的情况下

Promise#catch

只是promise.then(undefined, onRejected)的一个别名

Promise#then和Promise#catch都会返回一个和调用者不同的promise对象

Promise.all

Promise.all接受一个promise对象的数组作为参数,当这个数组里的所有promise对象全部变成resolve或reject状态的时候,它才会去调用.then方法。

传递给Promise.all的promise并不是一个个的顺序执行的,而是同时开始、并行执行的

Promise.race

只要有一个promise对象进入Fulfilled或者Rejected状态的话,就会继续进行后面的处理。在第一个Promise对象变为Fulfilled后,并不会取消其他promise对象的执行

使用Promise.race实现XHR取消

function delayPromise(ms) {
  return new Promise(function(resolve) {
    setTimeout(resolve, ms);
  }); 
}

function timeoutPromise(promise, ms) {
  var timeout = delayPromise(ms).then(function() {
    throw new Error("Operation timed out after" + ms + ' ms');
  });
  return Promise.race([promise, timeout]);
}

// 示例
function cancelableXHR(URL) {
    var req = new XMLHttpRequest();
  var promise = new Promise(function(resolve, reject) {
        req.open('GET', URL, true);
    req.onload = function() {
      if (req.status = 200) {
        resolve(req.responseText);
      } else {
        reject(new Error(req.statusText));
      }
    };
    req.onerror = function(){
      reject(new Error(req.statusText));
    }
    req.onabort = function() {
            reject(new Error('abort this request'));
    };
    req.send();
  });

  var abort = function() {
    if (req.readyState !== XMLHttpRequest.UNSENT) {
      req.abort();
    }
  };
  return {
        promise: promise,
    abort: abort
  };
}

var object = cancelableXHR('http://xxx/xx');

timeoutPromise(object.promise, 1000).then(function(value) {
  console.log("taskPromise在规定时间内结束:" + value);
}).catch(function(error) {
    console.log("发生超时", error);
})

Promise/A+规范

Promise的状态

Promise必须处于pending,resolved,rejected三个状态之一

  • 当Promise处于pending状态时可以转换到resolvedrejected状态
  • 当Promise处于resolved状态时无法再转换到其他状态,并且有一个无法改变value
  • 当Promise处于rejected状态时无法再转换到其他状态,并且有一个无法改变的reason(reason一般为一个Error对象)
Promise的then方法

Promise的then方法接受两个参数

promise.then(onResolved, onRejected);
  • onResolvedonRejected参数都是可选的,如果onResolvedonRejected不是function,则忽略相应的参数。onResolvedonRejected都不能被调用超过一次。

  • onResolvedonRejected需要通过异步的方式执行,可以用“macro-task”或“micro-task”机制来执行。

  • 同一个Promise的then方法可以被调用多次,当该Promise状态变为resolvedrejected状态时,注册在该Promise上的回调应该根据注册的顺序被调用。

  • then方法会返回一个Promise

promise2 = promise1.then(onResolved, onRejected);
1. 如果`onResolved`或`onRejected`返回一个`x`,那么`promise2`的状态需要根据`x`来决定(至于如何决定`promise2`的状态,会在第三部分中说明)。
2. 如果`onResolved`或`onRejected`抛出一个异常`e`,那么`promise2`必须rejected且`reason = e`。
3. 如果`promise1`是resolved状态且`onResolved`不是一个function那么`promise2`必须resolved,并且`promise2`的value必须与`promise1`相同
4. 如果`promise1`是rejected状态且`onRejected`不是一个function那么`promise2`必须rejected,并且`promise2`的reason必须与`promise1`相同

The Promise Resolution Procedure

这个操作为了兼容不同的PromiseA+标准,具体的步骤如下:

  • 1.如果xpromise是同一个对象的引用(x === promise),那么reject promise并将一个TypeError赋值给reason
  • 2.如果x是一个Promise(x instanceof Promise),那么promise的状态入下:
    • 2.1 如果x处于pending状态那么promise也处于pending状态,直到x状态变为resolved或rejected。
    • 2.2 如果x处于resolved状态,那么用x的value来resolve promise
    • 2.3 如果x处于rejected状态,那么用x的reason来reject promise
  • 3.如果x是一个对象或function
    • 3.1 如果获取属性x.then的过程中抛出异常e,那么将e作为reason来reject promise
    • 3.2 如果x.then是一个function,那么调用x.then传入参数resolvePromiserejectPromise
    • 3.2.1 如果resolvePromise被调用且传入的参数为y,那么再次执行此操作,参数为(promise, y)
    • 3.2.2 如果rejectPromise被调用且传入的参数r,那么将r作为reason来reject promise
    • 3.2.3 如果resolvePromiserejectPromise同时被调用,或者被调用多次,那么优先处理第一次调用,之后的调用都应该被忽略。
    • 3.2.4 如果调用x.then抛出了异常e,若在抛出异常前resolvePromiserejectPromise已经被调用,那么忽略异常即可。若resolvePromiserejectPromise没有被调用过,那么将e作为reason来reject promise
    • 3.3 如果x.then不是一个function,那么用x来resolve promise
  • 4.如果x既不是对象也不是function,那么用x来resolve promise
Promise实现
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

function myPromise(fn) {
  let _this = this;
  _this.currentState = PENDING; // 初始化状态为pending
  _this.value = undefined;

  // 用于保存 then 中的回调,只有当 promise状态为 pending 时才会缓存,并且每个实例至多缓存一个
  _this.resolvedCallbacks = [];
  _this.rejectedCallbacks = [];

  _this.resolve = function (value) {
    // 如果value是一个promise,则递归执行
    if (value instanceof myPromise) {
      return value.then(_this.resolve, _this.reject);
    }
    setTimeout(() => {
      if (_this.currentState === PENDING) {
        _this.currentState = RESOLVED;
        _this.value = value;
        _this.resolvedCallbacks.forEach(cb => cb());
      }
    })
  };

  _this.reject = function (reason) {
    setTimeout(() => {
      if (_this.currentState === PENDING) {
        _this.currentState = REJECTED;
        _this.value = reason;
        _this.rejectedCallbacks.forEach(cb => cb());
      }
    })
  }

  try {
    fn(_this.resolve, _this.reject);
  } catch (e) {
    _this.reject(e);
  }
}

myPromise.prototype.then = function (onResolved, onRejected) {
  const self = this;

  // 规范2.2.7,then必须返回一个新的promise
  let promise2;
  // 规范2.2 onResolved 和 onRejected 都为可选参数
  onResolved = typeof onResolved === 'function' ? onResolved : v => v;
  onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r };

  if (self.currentState === RESOLVED) {
    return (promise2 = new myPromise(function (resolve, reject) {
      // 规范2.2.4保证onFulilled,onReject异步执行,用setTimeout函数
      setTimeout(function () {
        try {
          const x = onResolved(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      })
    }));
  }

  if (self.currentState === REJECTED) {
    return (promise2 = new myPromise(function (resolve, reject) {
      setTimeout(function () {
        try {
          const x = onRejected(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      })
    }));
  }

  if (self.currentState === PENDING) {
    return (promise2 = new myPromise(function (resolve, reject) {
      self.resolvedCallbacks.push(function () {
        try {
          const x = onResolved(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (r) {
          reject(r);
        }
      })

      self.rejectedCallbacks.push(function () {
        try {
          const x = onRejected(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (r) {
          reject(r);
        }
      })
    }))
  }
}

// 规范2.3
function resolutionProcedure(promise2, x, resolve, reject) {
  // 规范2.3.1,x不能和promise2相同,避免循环引用
  if (promise2 === x) {
    return reject(new TypeError('Error'));
  }

  // 规范2.3.2 如果x为Promise,状态为pending需要继续等待,否则执行
  if (x instanceof myPromise) {
    if (x.currentState === PENDING) {
      x.then(function (value) {
        resolutionProcedure(promise2, value, resolve, reject);
      }, reject);
    } else {
      x.then(resolve, reject);
    }
    return;
  }

  // 规范2.3.3.3.3 reject或者resolve其中一个执行过的话,忽略其他
  let called = false;
  // 规范2.3.3 判断x是否为对象或者函数
  if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    // 规范2.3.3.2,如果不能取出then,就reject
    try {
      // 规范2.3.3.1
      let then = x.then;
      if (typeof then === 'function') {
        // 规范2.3.3.3
        then.call(x, y => {
          if (called) return;
          called = true;
          // 规范2.3.3.3.1
          resolutionProcedure(promise2, y, resolve, reject);
        }, e => {
          if (called) return;
          called = true;
          reject(e);
        })
      } else {
        // 规范2.3.3.4
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    // 规范2.3.4, x为基本类型
    resolve(x);
  }
}

myPromise.deferred = function () {
  const defer = {}
  defer.promise = new myPromise((resolve, reject) => {
    defer.resolve = resolve
    defer.reject = reject
  })
  return defer
}

try {
  module.exports = myPromise
} catch (e) {
}