Promise A+ 手写一个 Promise
Promise 规范文档 https://promisesaplus.com/
Promise 术语¶
promise
is an object or function with athen
method.thenable
is an object or function that defines athen
method.value
is any legal JavaScript value (including undefined, a thenable, or a promise).exception
is a value that is thrown using the throw statement.reason
is a value that indicates why a promise was rejected.
需求¶
Promise States¶
一个 promise 有三中状态:pending, fulfilled, 或 rejected。
- 当 pending,可转化为 fulfilled 或 rejected。
- 当 fulfilled,状态不会再改变,记录一个 value 值。
- 当 rejected,状态不会再改变,记录一个 reason 值。
new Promise¶
- new Promise 时,需要传递一个 executor 执行器,执行器立刻执行
- executor 接受两个参数,分别是 resolve 和 reject
then
Method¶
then 方法有两个参数,分别访问自己当前或最终的 value or reason。
promise.then(onFulfilled, onRejected)
- 如果调用 then 时,promise 已经成功,则执行 onFulfilled,并将 promise 的 value 作为参数传递进去。
- 如果promise已经失败,那么执行 onRejected, 并将 promise 失败的 reason 作为参数传递进去。
- 如果 promise 的状态是 pending,需要将 onFulfilled 和 onRejected 函数存放起来,等待状态确定后,再依次将对应的函数执行(发布-订阅)。
- promise 可以 then 多次,promise 的 then 方法返回一个 promise。
- 如果 then 返回的是一个结果,那么就会把这个结果作为参数,传递给下一个 then 的成功的回调(onFulfilled)。
- 如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调(onRejected)。
- 如果 then 返回的是一个 promise,那么需要等这个 promise 执行完(递归);如果成功,就走下一个then的成功,如果失败,就走下一个then的失败。
Promise Resolution Procedure¶
function resolvePromise(promise2, x, resolve, reject)
- 若 promise2 等于 x, reject 一个 TypeError。(死循环)
- 若 x 是一个 promise, 观察其状态:(递归)
- 如果 x 是 pending,等到 x fulfilled or rejected;
- 如果 x 是 fulfilled,resolve x 的 value;
- 如果 x 是 rejected,rejected x 的 reason。
- 如果 x 是 object or function,
- 让 then = x.then
- 如果 then 是 function,调用 then,调用者为 x,第一个参数 resolvePromise, 第二个参数 rejectPromise:
- resolvePromise 的参数是 y, 递归([[Resolve]])(promise2, y);
- rejectPromise 的参数是 r, 直接 reject(r );
- 如果 then 不是 function,resolve(x);
- 如果 x 不是 object or function,resolve(x);
An Example¶
var p = new Promise((resolve, reject) => {
// resolve(100);
setTimeout(() => {
resolve(110);
}, 1000);
// reject(200);
// throw new Error(300);
});
p.then(
val => {
console.log('in then1:', val);
return new Promise((resolve, reject) => {
// resolve('then1的结果');
// reject('then1的原因');
resolve(new Promise(resolve => resolve('then1的嵌套结果')));
});
},
reason => {
console.log('in err1:', reason)
return 'err1 的结果';
}
).then(
val => {
console.log('in then1.1:', val)
return 'then1.1的结果';
},
reason => {
console.log('in err 1.1:', reason);
}
).then(
val => console.log('in then1.1.1:', val),
reason => console.log('in err1.1.1:', reason)
);
p.then(
val => console.log('in then2:', val),
reason => console.log('in err2:', reason)
);
核心代码¶
// src/promise.js
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function Promise(executor) {
var self = this;
self.status = PENDING;
self.onFulfilledList = [];
self.onRejectedList = [];
self.value = undefined;
self.reason = undefined;
function resolve(value) {
if (self.status === PENDING) {
self.status = FULFILLED;
self.value = value;
self.onFulfilledList.forEach(fn => fn());
}
}
function reject(reason) {
if (self.status === PENDING) {
self.status = REJECTED;
self.reason = reason;
self.onRejectedList.forEach(fn => fn());
}
}
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
// promise a+ 2.2
Promise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
const self = this;
let promise2 = new Promise((resolve, reject) => {
if (self.status === FULFILLED) {
/* promise a+ 2.2.4 --> setTimeout
* onFulfilled or onRejected must not be called
* until the execution context stack contains only platform code.
*/
setTimeout(() => { // 宏任务
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e);
}
});
}
if (self.status === REJECTED) {
setTimeout(() => { // 宏任务
try {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e);
}
});
}
if (self.status === PENDING) {
self.onFulfilledList.push(() => {
setTimeout(() => { // 宏任务
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e);
}
});
});
self.onRejectedList.push(() => {
setTimeout(() => { // 宏任务
try {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch(e) {
reject(e);
}
})
});
}
});
return promise2;
};
// promise a+ 2.3
function resolvePromise(promise2, x, resolve, reject) {
// promise a+ 2.3.1
if (promise2 === x) {
reject(new TypeError('Chaining cycle detected for promise'));
return;
}
// promise a+ 2.3.3
if (typeof x === 'object' && x !== null || typeof x === 'function') {
let called; // promise a+ 2.3.3.3.3 防止成功和失败重复调用
try {
let then = x.then;
if (typeof then === 'function') {
// promise a+ 2.3.3.3
then.call(x, y => {
// resolve(y); // 直接 fulfill: y 是普通值
if (called) { return; }
called = true;
resolvePromise(promise2, y, resolve, reject); // 递归: y 是 promise
}, r => {
if (called) { return; }
called = true;
reject(r);
});
} else {
if (called) { return; }
called = true;
resolve(x); // 递归退出条件
}
} catch(e) {
if (called) { return; }
called = true;
reject(e);
}
} else {
resolve(x);
}
}
可将宏任务
setTimeout
替换为微任务API。谷歌浏览器的
queueMicrotask
类似 nodejs 的process.nextTick
。
Promise Deferred¶
代码¶
// src/promise.js 添加入口
Promise.defer = Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
};
用途与测试¶
- 可以减少一层嵌套。
const fs = require('fs');
function readFile(path) {
let dfd = Promise.defer();
fs.readFile(path, 'utf-8', (err, data) => {
if (err) { dfd.reject(err); }
dfd.resolve(data);
});
return dfd.promise;
}
readFile('./demo.txt').then(data => console.log(data));
Promise A+ 测试用例¶
- 前提1 添加入口
module.exports = Promise;
- 前提2 实现
Promise.deferred
$ promises-aplus-tests src/promise.js
Promise.all¶
- 全部成功才算成功,否则一个失败就返回失败。
代码¶
function isPromise(val) {
return (
typeof val === 'function' ||
typeof val === 'object' && val !== null
) && typeof val.then === 'function'
}
Promise.all = function(arr) {
return new Promise((resolve, reject) => {
let result = [];
let index = 0;
for(let i = 0; i < arr.length; i++) {
if (isPromise(arr[i])) {
arr[i].then(value => {
result[i] = value;
if(++index === arr.length) {
resolve(result);
}
}, reject)
} else {
result[i] = arr[i];
if(++index === arr.length) {
resolve(result);
}
}
}
});
}
测试¶
Promise.all([1, 2, readFile('./demo.txt'), 4 ]).then(value => console.log(value));
Promise.resolve¶
Promise.resolve = function(value) {
return new Promise((resolve, reject) => {
resolve(value);
});
}
Promise.reject¶
Promise.reject = function(reason) {
return new Promise((resolve, reject) => {
reject(reason);
});
};