推理很简单,但不一定是不言而喻的。
-
.then()
返回一个承诺
- if
then
在 Promise 的子类上调用,返回的 Promise 是子类的实例,而不是 Promise 本身。
- the
then
返回的 Promise 是通过调用子类构造函数来构造的,并向其传递一个内部执行器函数,该函数记录了resolve
and reject
传递给它的参数供以后使用。
- “稍后使用”涵盖解决或拒绝返回的承诺
then
异步监视执行时onfulfilled
or onrejected
处理程序(稍后)以查看它们是否返回一个值(这解决了then
返回的承诺)或抛出错误(拒绝承诺)。
简而言之then
内部调用获取并记录对resolve
and reject
他们返回的承诺的功能。
So regarding the question,
new MyPromise( 'p1')
工作正常,并且是对子类构造函数的第一次调用。
.then( someFunction)
records someFunction
在列表中then
拨打的电话new MyPromise
(记起then
可以多次调用)并尝试通过调用创建返回承诺
new MyPromise( (resolve, reject) => ... /* store resolve reject references */
这是对子类构造函数的第二次调用then
代码。构造函数预计(并且确实)同步返回。
在创建返回承诺后返回时,.then
方法进行完整性检查以查看是否resolve
and reject
后面需要用到的函数其实就是函数。它们应该与提供的回调一起存储(在列表中)then
call.
如果是MyPromise
他们不是。执行者路过then
, to MyPromise
,甚至没有被调用。所以then
方法代码抛出类型错误“Promise解析或拒绝函数不可调用” - 它无法解析或拒绝它应该返回的promise。
创建 Promise 的子类时,子类构造函数必须以执行器函数作为第一个参数,并用 real 调用执行器resolve
and reject
函数参数。这是内部要求的then
方法代码。
做一些复杂的事情MyPromise
,也许检查第一个参数以查看它是否是一个函数,如果是,则将其作为执行器调用,可能是可行的,但超出了本答案的范围!对于所示的代码,编写工厂/库函数可能更简单:
function namedDelay(name, delay=1000, value=1) {
var promise = new Promise( (resolve,reject) => {
setTimeout(() => {
resolve(value)
}, delay)
}
);
promise.name = name;
return promise;
}
namedDelay( 'p1')
.then(result => {
console.log('fulfilled, result: ', result)
})
.catch(err => {
console.error('err: ', err)
})
;TLDR
Promise 的类扩展不是扩展。如果是的话,则需要实现 Promise 接口并采用执行器函数作为第一个参数。您可以使用工厂函数返回异步解析的 Promise(如上所述),或者hack发布的代码与
MyPromise.prototype.constructor = Promise
什么导致.then
返回一个常规的 Promise 对象。黑客本身驳斥了正在发生类扩展的想法。
Promise 扩展示例
以下示例显示了一个基本的 Promise 扩展,它添加了提供给构造函数的属性。值得注意的是:
-
The Symbol.toString getter https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toStringTag#custom_tag_with_tostringtag只影响将实例转换为字符串的输出。记录实例时,它不会将“Promise”更改为“MyPromise”object在经过测试的浏览器控制台上。
-
Firefox 89 (Proton) 不报告扩展实例自己的属性,而 Chrome 则报告 - 下面的测试代码按名称记录实例属性的原因。
class MyPromise extends Promise {
constructor(exec, props) {
if( typeof exec != "function") {
throw TypeError( "new MyPromise(executor, props): an executor function is required");
}
super((resolve, reject) => exec(resolve,reject));
if( props) {
Object.assign( this, props);
}
}
get [Symbol.toStringTag]() {
return 'MyPromise';
}
}
// Test the extension:
const p1 = new MyPromise( (resolve, reject) =>
resolve(42),
{id: "p1", bark: ()=>console.log("woof") });
console.log( "p1 is a %s object", p1.constructor.name);
console.log( "p1.toString() = %s", p1.toString());
console.log( "p1.id = '%s'", p1.id);
console.log( "p1 says:"); p1.bark();
const pThen = p1.then(data=>data);
console.log( "p1.then() returns a %s object", pThen.constructor.name);
let pAll = MyPromise.all([Promise.resolve(39)]);
console.log( "MyPromise.all returns a %s object", pAll.constructor.name);
try { new MyPromise(); }
catch(err) {
console.log( "new MyPromise() threw: '%s'", err.message);
}