francecil / leetcode

solutions about leetcode
MIT License
9 stars 1 forks source link

实现一个 Promise #9

Closed francecil closed 4 years ago

francecil commented 5 years ago

参考 es6-promise

francecil commented 4 years ago

完整实现还是挺耗时的,这里仅以 then 方法为切入点,实现部分功能

francecil commented 4 years ago

前言

先谈规范,再分析 polyfill 源码,最后实例解析。

规范

规范直接参考这边:【翻译】Promises/A+规范

返回值这部分重要且易错,这里单独提出来分析下

首先,then 方法必须返回一个 promise 对象

  promise2 = promise1.then(onFulfilled, onRejected);

Promise 解决过程 [[Resolve]](promise, x)

promise 为 then 回调的返回值,运行 [[Resolve]](promise, x) 需遵循以下步骤:

如果一个 promise 被一个循环的 thenable 链中的对象解决,而 [[Resolve]](promise, thenable) 的递归性质又使得其被再次调用,根据上述的算法将会陷入无限递归之中。算法虽不强制要求,但也鼓励施者检测这样的递归是否存在,若检测到存在则以一个可识别的 TypeError 为据因来拒绝 promise

这个目前还不知道怎么写才能产生循环的 thenable 链 - -

原理

then 操作解析

全局维护一个 microTask Queue ,每个 promise 自身维护一个 queue

当 promise 执行 then 方法时:

  1. 令 then 方法中的回调为 resolver,令 promise 的内部值为 outcome
  2. 创建一个 PENDING 状态的 Promise 对象 promise2
  3. 如果 promise 处于 PENDING 状态,将 “执行 resolver ,改变 promise2 状态” 作为队列项放入该 promise 对象自身维护的 queue
  4. 否则将 “执行 resolver ,改变 promise2 状态” 作为 microTask 放入 microTask Queue
  5. 返回 promise2

当同步代码执行完毕,开始执行 microTask Queue 中的任务

执行 microTask :

  1. 以 promise 的内部值 outcome 作为参数执行 resolver 并得到返回值 returnValue
  2. 若出现异常,将异常赋值给 returnValue
  3. 若未出现异常,对 returnValue 做些处理,这里不细讲
  4. 修改 promise2 的状态为 FULFILLED/REJECTED ,并修改 promise2 的内部值 outcome 为 returnValue
  5. 该 promise2 维护的 queue 中的队列项放入 microTask Queue

不断的执行 microTask 直到 microTask Queue 为空,一轮 event loop 结束

源码

lie.js (一个 Promise polyfill 库) 的代码做个精简,仅考虑普通的 then 回调和 resolve 方法

然后 microTask 任务执行接口我们直接用的 setTimeout ,不搞 Mutation 那些幺蛾子,代码如下

// immediate.js

var scheduleDrain = function () {
  setTimeout(nextTick, 0);
};

var draining;
var queue = [];

// 在同步代码 task 执行之后,模拟清空 microTask queue
// 由于执行过程中可能有新的 microTask 所以用了双层循环
function nextTick() {
  draining = true;
  var i, oldQueue;
  var len = queue.length;
  while (len) {
    oldQueue = queue;
    queue = [];
    i = -1;
    while (++i < len) {
      oldQueue[i]();
    }
    len = queue.length;
  }
  draining = false;
}

function immediate(task) {
  if (queue.push(task) === 1 && !draining) {
    scheduleDrain();
  }
}

Promise 代码如下

// promise.js

// then 方法执行时作为参数 resolver 实例化 Promise
function INTERNAL () { }

const REJECTED = 'REJECTED';
const FULFILLED = 'FULFILLED';
const PENDING = 'PENDING';

function Promise (resolver) {
  if (typeof resolver !== 'function') {
    throw new TypeError('resolver must be a function');
  }
  this.state = PENDING;
  this.queue = [];
  this.outcome = void 0;
  // 外部通过 new 实例化时执行
  if (resolver !== INTERNAL) {
    safelyResolveThenable(this, resolver);
  }
}

Promise.prototype.then = function (onFulfilled, onRejected) {
  // 特殊参数的处理
  if (typeof onFulfilled !== 'function' && this.state === FULFILLED ||
    typeof onRejected !== 'function' && this.state === REJECTED) {
    return this;
  }
  // 创建一个 PENDING 状态的 promise
  var promise = new this.constructor(INTERNAL);
  // 根据原 promise 状态进行不同处理
  if (this.state !== PENDING) {
    var resolver = this.state === FULFILLED ? onFulfilled : onRejected;
    makeMicroTask(promise, resolver, this.outcome);
  } else {
    this.queue.push(new QueueItem(promise, onFulfilled, onRejected));
  }

  return promise;
};

Promise.resolve = function (value) {
  if (value instanceof this) {
    return value;
  }
  return handleResolve(new this(INTERNAL), value);
}

// promsie 内部队列项
function QueueItem (promise, onFulfilled, onRejected) {
  this.promise = promise;
  // 仅考虑 function类型的 onFulfilled
  this.onFulfilled = onFulfilled;
  this.callFulfilled = function(value) {
    makeMicroTask(this.promise, this.onFulfilled, value);
  };
  // 暂不处理 onRejected
}

// 将 handleResolve 操作放入 microtask
function makeMicroTask (promise, func, value) {
  immediate(function () {
    handleResolve(promise, func(value));
  });
}

// 改变 promise 状态,并通知 queue 中的 promise 执行 makeMicroTask
function handleResolve (self, value) {
  self.state = FULFILLED;
  self.outcome = value;
  var i = -1;
  var len = self.queue.length;
  while (++i < len) {
    self.queue[i].callFulfilled(value);
  }
  return self;
}

// 对 resolver 方法的两个参数(resolve,reject)进行包装
function safelyResolveThenable (self, thenable) {
  var called = false;
  function onError (value) {
    if (called) {
      return;
    }
    called = true;
    // 暂不处理 reject
    // handlers.reject(self, value);
  }
  function onSuccess (value) {
    if (called) {
      return;
    }
    called = true;
    handleResolve(self, value);
  }
  thenable(onSuccess, onError);
}

实例分析

new Promise(resolve => {
    console.log(1);
    resolve(3);
    Promise.resolve().then(()=> console.log(4))
}).then(num => {
    console.log(num)
});
console.log(2)
答案 1243
Promise.resolve().then(() => {
  console.log(1);
  Promise.resolve().then(()=> console.log(4))
}).then(() => {
    console.log(3)
});
console.log(2)
答案 2143
new Promise((r) => {
  r()
  console.log(1);
}).then(() => {
    console.log(11)
}).then(() => {
    console.log(12)
}).then(() => {
    console.log(13)
})
var promise = new Promise((r) => {
  r()
  console.log(2);
  let promise = Promise.resolve().then(()=> console.log(21)).then(()=> console.log(22)).then(()=> console.log(23))
  promise.then(()=>console.log(29))
  Promise.resolve().then(()=> console.log(24)).then(()=> console.log(25)).then(()=> console.log(26))
})
promise.then(() => {
    console.log(27)
})
promise.then(() => {
    console.log(28)
})
答案 【1,2,11,21,24,27,28,12,22,25,13,23,26,29】

如何测试

当我们的 polyfill 写完后,需要测试下是否符合规范,我们自己写测试用例肯定是不可能的,这里有一个测试库 promises-tests

const tests = require("promises-aplus-tests");
const Promise = require("./index");

const deferred = function() {
    let resolve, reject;
    const promise = new Promise(function(_resolve, _reject) {
        resolve = _resolve;
        reject = _reject;
    });
    return {
        promise: promise,
        resolve: resolve,
        reject: reject
    };
};
const adapter = {
    deferred
};
tests.mocha(adapter);

这样就可以做测试了

参考

  1. 从一道Promise执行顺序的题目看Promise实现
  2. 手写一个符合A+规范的Promise