Skip to main content

实现Promise

const STATUS = {
PENDING: 'PENDING',
FULFILLED: 'FULFILLED',
REJECTED: 'REJECTED',
}

class Promise {
constructor(executor) {
this.status = STATUS.PENDING;
this.value = null;
this.reason = null;

this.onResolvedList = [];
this.onRejectList = [];

const resolve = (value) => {
if (value instanceof Promise) {
return value.then(resolve, reject);
}

this.status = STATUS.FULFILLED;
this.value = value;

this.onResolvedList.forEach(cb => {
cb && cb();
});
}

const reject = (reason) => {
this.status = STATUS.REJECTED;
this.reason = reason;

this.onRejectList.forEach(cb => {
cb && cb();
});
}

try {
executor && executor(resolve, reject);
} catch (error) {
reject(error);
}
}

static resolve (value) {
return new Promise((resolve, reject) => {
resolve(value);
});
}

static reject (value) {
return new Promise((resolve, reject) => {
reject(value);
});
}

// 1. x === promise(它本身) => error
// 2. x => value => value
// 3. x 是 promise =>
// 3.1 => x.then =>
// 3.1.1 => function => value or error => resolve(value) or reject(error)
// 3.1.2 => value => value
resolvePromise (promise, x, resolve, reject) {
if (x === promise) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
}

let called;

const beCalled = called => {
if (called) {
return;
}

called = true;
}

if ((typeof x === 'object' && x !== null) || (typeof x === 'function')) {
const then = x.then;

try {
if (typeof then === 'function') {
then.call(
x,
y => {
beCalled(called);
resolvePromise(promise, y, resolve, reject);
},
e => {
beCalled(called);
reject(e);
}
);
} else {
resolve(x);
}
} catch (error) {
beCalled(called);
reject(error);
}
} else {
resolve(x);
}
}

then (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function'
? onFulfilled
: value => value;

onRejected = typeof onRejected === 'function'
? onRejected
: error => {
throw error
};

const promise = new Promise((resolve, reject) => {
if (this.status === STATUS.FULFILLED) {
setTimeout(() => {
try {
const x = onFulfilled(this.value)
this.resolvePromise(promise, x, resolve, reject);
} catch (error) {
reject(error);
}
});
}

if (this.status === STATUS.REJECTED) {
try {
setTimeout(() => {
try {
const x = onRejected(this.reason)
this.resolvePromise(promise, x, resolve, reject);
} catch (error) {
reject(error);
}
});

} catch (error) {
reject(error);
}
}

if (this.status === STATUS.PENDING) {
try {
this.onResolvedList.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value)
this.resolvePromise(promise, x, resolve, reject);
} catch (error) {
reject(error);
}
});
});

this.onRejectList.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason)
this.resolvePromise(promise, x, resolve, reject);
} catch (error) {
reject(error);
}
});
});
} catch (error) {
reject(error);
}
}
});

return promise;
}
}

Promise.prototype.catch

Promise.prototype.catch = cb => {
if (typeof cb !== 'function') {
return Promise.reject(new TypeError(`${cb} is not a function`));
}

return this.then(null, cb);
}

Promise.prototype.finally

Promise.prototype.finally = cb => {
if (typeof cb !== 'function') {
return Promise.reject(new TypeError(`${cb} is not a function`));
}

return this.then(
value => Promise.resolve(cb()).then(() => value),
reason => Promise.resolve(cb()).then(() => { throw reason; }),
);
}

Promise.prototype.retry

Promise.prototype.retry = (cb, times = 1) => {
if (typeof cb !== 'function') {
return Promise.reject(new TypeError(`${cb} is not a function`));
}

return new Promise((resolve, reject) => {
while(times--) {
try {
Promise.resolve(cb()).then(value => {
resolve(value);
break;
});
} catch (error) {
console.log(`Retry in the ${times}\'s timers.`)
}
}
}).catch (error) {
reject(error);
}
}

Promise.race

Promise.race = values => {
if (!Array.isArray(values)) {
const type = typeof values;
return new TypeError(`TypeError: ${type} ${values} is not iterable`)
}

return new Promise((resolve, reject) => {
for (let i = 0; i < values.length; i++) {
const value = values[i];

Promise.resolve(value).then(
val => resolve(val),
reject
);

// if (typeof value === 'function') {
// value.then(val => resolve(val), reject);
// } else {
// resolve(value);
// }
}
});
}

Promise.all

Promise.all = values => {
if (!Array.isArray(values)) {
const type = typeof values;
return new TypeError(`TypeError: ${type} ${values} is not iterable`)
}

return new Promise((resolve, reject) => {
const res = [];
const order = 0;

const processed = (value, i) => {
res[i] = value;

if (++order === values.length) {
resolve(res);
}
}

for (let i = 0; i < values.length; i++) {
const value = values[i];

Promise.resolve(value).then(
val => processed(val, i),
reject
);

// if (typeof value === 'function') {
// value.then(value => processed(value, i), reject);
// } else {
// processed(value, i);
// }
}
});
}

Promise.allSettled

// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
Promise.allSettled = promises => {
if (!Array.isArray(promises)) {
const type = typeof promises;
return new TypeError(`TypeError: ${type} ${promises} is not iterable`)
}

if (promises.length === 0) {
return Promise.resolve([]);
}

return Promise.all(promises.map(promise => {
return Promise.resolve(promise).then(
value => ({ status: 'fulfilled', value }),
reason => ({ status: 'rejected', reason }),
);
}));
}

Promise.any

// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/any
Promise.any = promises => {
if (!Array.isArray(promises)) {
const type = typeof promises;
return new TypeError(`TypeError: ${type} ${promises} is not iterable`)
}

if (promises.length === 0) {
return Promise.reject(new TypeError('any(): array must not be empty'));
}

return new Promise((resolve, reject) => {
let index = 0;
const rejected = [];

const onRejected = (reason, i) => {
index++;
rejected[index] = reason;

if (index === promises.length) {
reject(new AggregateError('No Promise in Promise.any was resolved'));
}
}

for (let i = 0; i < promises.length; i++) {
const value = promises[i];

Promise.resolve(value).then(
resolve,
reason => onRejected(reason, i)
);
}
});
}

wrapAbort

// 中断
function wrapAbort (promise) {
let abort;

const abortPromise = new Promise((resolve, reject) => {
abort = reject;
});

let p = Promise.race([promise, abortPromise]);

return p;
}

promisify

function promisify (fn) {
return (...args) => {
return new Promise((resolve, reject) => {
fn(
...args,
(reason, value) => {
if (reason) {
reject(reason);
}

resolve(value);
}
);
})
}
}

官方用例测试

npm i -D promises-aplus-tests

Promise.defer = Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
})
return dfd;
}

测试

const promise = new Promise((resolve, reject) => {
reject('失败');
}).then().then().then(data => {
console.log(data);
}, err => {
console.log('err', err);
})