本文代码基于上篇博客 手撕 Promise A+ 中的源码
原型对象上的 catch 方法
let promise = new Promise((resolve, reject) => {
reject('fail');
}).then().catch(err => {
console.log('err', err);
}).then(data => {
console.log('success', data);
});
// err fail
// success undefined
其实我们可以看到,catch 本质上也就是 then 方法,只是省略了成功回调罢了
// 原型上的 catch 方法
class _Promise {
// ...
catch(errFn) {
return this.then(null, errFn);
}
}
静态方法 resolve、reject
Promise.resolve('hello').then(data => {
console.log(data);
});
Promise.reject('hello').catch(reason => {
console.log(reason);
});
静态方法中的 resolve 和 reject 的区别在哪里?除了目标状态不同,还有就是 Promise.resolve 具备等待效果。
Promise.resolve(new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1000);
}, 1000)
})).then(data => {
console.log('success', data);
});
Promise.reject(new Promise((resolve, reject) => {
setTimeout(() => {
// 外层的 reject 静态方法不会等待此结果成功返回
resolve('reason msg');
}, 1000)
})).catch(reason => {
console.log('err', reason);
});
// 立即输出 "err Promise { <pending> }"
// 1s 后输出 "success 1000"
二者的实现:
class _Promise {
constructor() {
const resolve = (value) => {
// 这个方法并不属于规范中的,只是为了和原生表现一样,加了 value 为 promise 的判断逻辑
// 这个地方不能判断是否为 thenable 的对象「因为这样写 promise A+ 测试就不通过了」
if (value instanceof _Promise || value instanceof Promise) {
// 如果是一个 promise,我们增加等待效果
// return 必须加,阻断后面代码执行
// 根据 value 的执行状态,决定当前 promise 的状态
// 所以 Promise.resolve(x) 并不一定返回成功态,还要取决于内部参数的状态
return value.then(resolve, reject); // 递归
}
if (this.status !== PENDING) return;
this.value = value;
this.status = FULFILLED;
this.onResolvedCallbacks.forEach(cb => cb());
}
}
// ...
// 静态方法 具有等待效果的 resolve
static resolve(value) {
return new _Promise((resolve, reject) => {
// 当 value 是 promise 时,要做等待处理
// 所以此处要改写上面的 _Promise 中的 resolve 方法
resolve(value);
});
}
// 静态方法 reject
static reject(error) {
return new _Promise((resolve, reject) => {
reject(error);
});
}
}
测试代码
_Promise.resolve(new _Promise((resolve, reject) => {
setTimeout(() => {
resolve(1000);
}, 1000)
})).then(data => {
console.log('success', data);
});
_Promise.reject(new _Promise((resolve, reject) => {
setTimeout(() => {
// 外层的 reject 静态方法不会等待此结果成功返回
resolve('reason msg');
}, 1000)
})).catch(reason => {
console.log('err', reason);
});
// 直接输出 "err _Promise { status: 'PENDING', value: undefined, .. }"
// 1s 后输出 "success 1000"
知识点:手动实现 node 新增 promises 和 promisify
promises 是 node 里面新增的一个功能,它可以让 fs 的所有方法都变成 promise
const fs = require('fs').promises;
const util = require('util');
// 可以直接返回一个 promise 对象,通过 then 去调用
fs.readFile('./name.txt', 'utf8').then(data => {
console.log(data);
});
// 针对单一方法的 promise 风格转化
const promiseReadFile = util.promisify(fs.readFile);
promiseReadFile('./name.txt', 'utf8').then(data => {
console.log(data);
})
实现 promisify
/**
* @param {*} fn 要 promise 化的函数
* @returns function 返回的新函数
*/
function _promisify(fn) {
return function(...args) {
return new Promise((resolve, reject) => {
fn(...args, (err, data) => {
err && reject(err);
resolve(data);
})
})
}
}
const promiseReadFile = _promisify(fs.readFile);
promiseReadFile(path.join(__dirname, './1.txt'), 'utf8').then(data => {
console.log(data);
}, e => {
console.log('error', e);
})
实现 promises
// 实际上就是把传入的模块对象的属性「值为函数的」遍历执行 promisify
const _promisify = require('./_promisify');
/**
* @param { Object } 要转 promise 化的模块对象
* @return void
*/
function _promises(module) {
for (let key in module) {
let curVal = module[key];
if (typeof curVal === 'function') {
module[key] = _promisify(curVal);
}
};
}
_promises(fs);
fs.readFile(path.join(__dirname, './1.txt'), 'utf8').then(data => {
console.log(data);
})
静态方法 all
const fs = require('fs').promises;
Promise.all([fs.readFile('name.txt', 'utf8'), fs.readFile('age.txt', 'utf8')]).then(data => {
console.log(data);
});
// [ '杨帅', '18' ]
all 的实现
// 细节
// @1 all 方法接收一个迭代器对象,对象中的元素可以不是 promise 实例
// @2 all 方法全部成功才是成功态,任意一个失败则返回失败态度『对比下 allSettled』
// @3 all 方法需要保持有序输出
class _Promise {
// ...
// 静态方法 all
static all(promises) {
if (!promises || typeof promises[Symbol.iterator] !== 'function') {
throw new TypeError('object is not iterable');
}
let resolveCount = 0;
let result = [];
return new _Promise((resolve, reject) => {
[...promises].forEach((itm, idx) => {
_Promise.resolve(itm).then(data => {
result[idx] = data;
if (++resolveCount == promises.length) return resolve(result);
}, reason => {
return reject(reason);
});
});
});
}
}
测试代码
const fs = require('fs').promises;
let p1 = fs.readFile('./_Promise7/name.txt', 'utf8');
let p2 = fs.readFile('./_Promise7/age.txt', 'utf8');
_Promise.all([p1, p2]).then(data => {
console.log('success', data);
}, reason => {
console.log('err', reason);
});
// success [ '杨帅', '18' ]
原型对象上的 finally 方法
// 前置知识
// @1 finally 不接收参数,会把上一个 then 的参数原封不动往下传递
// @2 finally 无论成功失败都会执行
// @3 finally 如果返回的是一个 promise,那么会有等待效果
Promise.resolve('ok').finally((...args) => {
console.log(args);
}).then(data => {
// 值能穿透
console.log('成功', data); // 成功 'ok'
}).catch(e => {
console.log('失败', e);
});
// []
// 成功 'ok'
其实看到这里,我们就应该能想到,它就是用 then 来实现的,于是这个代码就很好写了。
class _Promise {
//...
finally(cb) {
return this.then(data => {
cb();
return data; // 传递值
}, reason => {
cb();
throw reason;
});
}
}
不过还有一些特殊的情况
// 只要链式经过 finally,且 finally 返回一个 promise ,不论成功还是失败都具备等待效果
// + 如果 finally 内部返回的 promise 状态为成功,则走成功回调,但不会采用成功的值
// + 如果 finally 内部返回的 promise 状态为失败,则走失败回调,会采用成功的值💦
Promise.resolve('ok').finally(() => {
return new Promise(resolve => {
setTimeout(() => {
resolve('xxxxxxx');
}, 1000);
})
}).then(data => {
console.log('成功', data); // 1s 后输出 "成功 ok"
}).catch(e => {
console.log('失败', e);
});
Promise.resolve('ok').finally(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('xxxxxxx');
}, 1000);
})
}).then(data => {
console.log('成功', data);
}).catch(e => {
console.log('失败', e); // 1s 后输出 "失败 xxxxxxx"
});
所以需要继续补全 finally 的源码
// 思路
// @1 不知道 finally 内部函数返回值是不是 promsie,我们手动给它转成 promise
// @2 这里都用的 Promise.resolve 方法,因为它具备等待效果
// @3 内部函数返回成功态的 promise,则往下传递上一次的值 val
// @4 内部函数返回失败态的 promise,则透传 promise 的失败原因 reason
// @5 理解 Promise.resolve(x).then() 的精髓在于 Promise.resolve(x) 并不一定走成功回
// 调,而是根据 x 的值来决定,如果 x 是一个失败状态的 promise,则整体返回一个失败态
// 的 promise,具体参见源码 resolve 内部第一行递归调用。
// @6 Promise.resolve(x).then() 没有传递失败回调,所以当 Promise.resolve(x) 返回的
// 是失败态的 promise 时,直接把该 promise 的状态和值透传下去,比较取巧
class _Promise {
//...
finally(cb) {
let x = cb(); // 函数返回值
// val -> 上面传递的值,用于往下传递
return this.then(val => {
// 成功回调,Promise.resolve('ok').finally(cb) 会走这里
// + Promise.resolve(x) 返回成功态,则往下传递上一次的值 val
// + Promise.resolve(x) 返回失败态,也就是 x 为 rejected 的 promise,则 x
// 状态和值往下传递(因为没写失败回调)
return Promise.resolve(x).then(data => val);
}, reason => {
// 失败回调,Promise.reject('ok').finally(cb) 会走这里
// + Promise.resolve(x) 返回成功态,则往下传递上一次的值 reason
// + Promise.resolve(x) 返回失败态,也就是 x 为 rejected 的 promise,则 x
// 状态和值往下传递(因为没写失败回调)
return Promise.resolve(x).then(x => { throw reason });
});
}
}
测试代码
// _Promise.resolve('ok').finally((...args) => {
// console.log(args);
// }).then(data => {
// // 值能穿透
// console.log('成功', data); // 成功 'ok'
// }).catch(e => {
// console.log('失败', e);
// });
// []
// 成功 'ok'
_Promise.resolve('ok').finally(() => {
return new _Promise((resolve, reject) => {
setTimeout(() => {
reject('xxxxxxx');
}, 1000);
})
}).then(data => {
console.log('成功', data);
}).catch(e => {
console.log('失败', e);
});
// 1s 后输出 "失败 xxxxxxx"
静态方法 race
Promise.race 方法,只要有一个成功或者失败,则返回结果成功或失败。
const fs = require('fs').promises;
const path = require('path');
let p1 = fs.readFile(path.join(__dirname, './1.txt'), 'utf8');
let p2 = fs.readFile(path.join(__dirname, './2.txt'), 'utf8');
Promise.race([p1, p2]).then(data => {
console.log(data);
}, e => {
console.log('error', e);
});
这里实现很巧妙,同时也有很多类似的场景,如果外层 promise 依赖内层 promise 对象的状态,就把外层的 resolve 和 reject 当初内层 promise 的成功和失败回调传入,其实和源码中 resolve 实现类似。
/**
* @param { Iterable } 可迭代对象
* @returns promise 对象
*/
function _race(Iterable) {
return new Promise((resolve, reject) => {
for (let itm of Iterable) {
if (itm && typeof itm.then === 'function') {
// promise 对象 这里很巧妙的把外层 resolve, reject 传入了
itm.then(resolve, reject);
} else {
resolve(itm);
}
}
});
}
module.exports = _race;
race 的使用场景
有一个需求,做图片懒加载时,我希望接口如果超过 2s 未返回结果,就超时
// 我可能会这么写
let abort;
let p = new Promise((resolve, reject) => {
abort = reject; // 保存 reject 方法
setTimeout(() => {
resolve('图片加载完成');
}, 3000);
});
setTimeout(() => {
abort('图片加载失败');
}, 2000);
p.then(val => {
console.log('success', val);
}, e => {
console.log('error', e);
});
很明显,写的比较繁琐,而且无法复用,而且我们改了原有的 p 的逻辑「保存了它的 reject」,我们借用 race 来改写下。
function wrap(old) {
let abort;
// 内置了一个 promise 我们可以控制这个 promise 来影响 promise.race 的结果
let p2 = new Promise((resolve, reject) => {
abort = reject; // 保存 reject 方法
});
let returnPromise = Promise.race([old, p2]);
returnPromise.abort = abort; // 把 abort 挂到 race 返回的 promise 对象上
return returnPromise;
}
使用起来就方便很多,并且不会动原 promise,随意定制了
let newPromise = wrap(p);
// 2s 超时
setTimeout(() => {
newPromise.abort('图片加载失败');
}, 2000);
newPromise.then(val => {
console.log('success', val);
}, e => {
console.log('error', e);
});
思考:resole 返回值的状态
// promise.resolve 一定走成功回调么
Promise.resolve(new Promise((resolve, reject) => {
reject('fail');
})).then(data => {
console.log(data)
}, {
reason => console.log('reason', reason)
})
// resolve 一定走成功回调么
var promise = new Promise((resolve, reject) => {
resolve(new Promise((resolve, reject) => {
reject(1);
}));
});
promise.then(val => {
console.log(val, 'success');
}, reason => {
console.log(reason, 'fail');
});
思考:如何中断 promise
- 如果仅仅是不采用原有 promise 的返回值,我们可以使用上面提到的 promise.race 方法,使用 abort('reject reason') 来提前返回值。
- 如果想破坏链式调用,我们返回一个 pending 状态的 promise 对象即可。
Promise.resolve('1').then(data => {
console.log(data);
return new Promise(() => {});
}).then(data => {
console.log(data);
}).then(data => {
console.log(data);
})
// 1