1

Promise A+规范的实现

 2 years ago
source link: https://icodex.me/2022/01/02/Promise%20A+%E8%A7%84%E8%8C%83%E7%9A%84%E5%AE%9E%E7%8E%B0/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Promise A+规范的实现

2022年1月2日 · One min read
Front End Engineer

Promise/A+

https://promisesaplus.com/#terminology

Promise 对象

  • 一个对象或者函数,并且带有then方法
  • 一个当前状态state,可以是pendingfulfilledrejected,且fulfilledrejected不可被改变
  • 一个resolvevalue,可以是任何 JS 值的形式
  • 一个执行reject的原因reason

then 方法

newPromise = promise.then(onFulfilled, onRejected)

then方法接收两个参数,且必须都是函数,否则忽略该参数

  • onFulfilled:当前 Promise 对象的状态变成fulfilled以后执行,并且将value作为第一个参数,并且只允许执行一次
  • onRejected:当前 Promise 对象的状态变成rejected以后执行,并且将reason作为第一个参数,并且只允许执行一次
  • onFulfilledonRejected必须在当前宏任务执行完以后才能执行,也就是异步的需求
  • then执行完返回一个新的 Promise 对象newPromise
    • 如果onFulfilled或者onRejected返回新的值x,则执行下文的方法Resolve(newPromise, x)
    • 如果onFulfilled或者onRejected执行抛出异常epromise2也必须reject(e)
    • 如果onFulfilled不是函数(未提供),则当promise状态变成fulfilled的时候,newPromise内部状态也要变成fulfilled并以promise内部的value执行resolve;同理onRejected也是
  • 同一个 Promise 对象的then方法可能调用多次
    • 并且各自onFulfilled或者onRejected回调必须按照调用then的顺序依次执行

Resolve(promise, x)

Resolve(promise, x)这里是描述then执行以后的算法判断,也就是then的反复执行过程,这是一个特殊的内部方法,是处理then链式执行的算法;

  • 如果promise === x,直接reject promise并返回TypeError的错误;
  • 如果x是一个 Promise 对象,则当前 Promise 对象promise的状态必须和x同步;
  • 如果x是一个对象或者函数:
    • 判断其是否具有then方法;
    • 如果没有,则reject
    • 如果具有then,则使用x作为then内部的this执行then,第一个参数传递resolvePromise,第二个参数传递rejectPromise
      • resolvePromise被传递参数y调用的时候,执行Resolve(promise, y)
      • rejectPromise被传递参数r调用的时候,则promisereject
      • 如果resolvePromiserejectPromise都被调用了,或者被多次调用,首次调用优先执行,后续被忽略;
      • 如果执行then抛出异常,当resolvePromise或者rejectPromise被调用了,则忽略;否则reject顶层的promise
  • 如果x不满足上述条件,promise直接fulfilled

实现

如果按照 promise-aplus 的算法慢慢摸索就可以直接实现一个完整的 Promise

初始化

Promise 对象在初始化以后,具有以上提到的valuestate以及reason等状态值

class APromise {
constructor(fn) {
if (typeof fn !== 'function') {
throw new TypeError('init parameter must be a function');
}

this.state = 'pending';
this.value = null;
this.reason = null;

try {
fn(this.resolve, this.reject);
} catch (e) {
this.reject(e);
}
}

resolve(value) {
setTimeout(() => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
}
});
}

reject(reason) {
setTimeout(() => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
}
});
}
}

then

在上述代码的基础上,添加then方法,因为then方法可能被调用多次,所以我们需要额外定义两个队列保存在 Promise 状态发生变化以后的回调函数onFulfilledonRejected,这里做一个参数非函数类型的简单化处理,这样只需要考虑函数的回调形式,毕竟日常开发也不会传递其他类型的参数

class APromise {
constructor(fn) {
this.state = 'pending';
this.value = null;
this.reason = null;
this.fulfilledQue = [];
this.rejectedQue = [];

fn(this.resolve, this.reject);
}

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

this.rejectedQue.push(
typeof onRejected === 'function'
? onRejected
: reason => {
throw reason;
},
);
};
}

然后then方法返回一个新的 Promise 对象newPromise,并且这个新的 Promise 对象和当前 Promise 的状态保持同步,所以在then内部需要判断当前 Promise 的状态来进行不同的处理:

  • 如果是fulfilled状态,则表明当前 Promise 已经执行了resolve,所以应该以当前 Promise 的value异步执行onFulfilled,并把当前 Promise 的value传递下去;rejected状态同理
  • 而如果当前 Promise 状态是pending,那么应该将onFulfilledonRejected推到回调函数中去,等待状态改变的时候自动调用
then = (onFulfilled, onRejected) => {
onFulfilled =
typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected =
typeof onRejected === 'function'
? onRejected
: reason => {
throw reason;
};
const newPromise = new APromise((resolve, reject) => {
if (this.state === 'fulfilled') {
// https://promisesaplus.com/#point-43
// 这里直接异步执行回调函数,保证返回的 Promise 状态和当前 Promise 状态一致
setTimeout(() => {
try {
const x = onFulfilled(this.value);
this.resolvePromise(newPromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}

if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason);
this.resolvePromise(newPromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}

// 如果当前 Promise 状态
if (this.state === 'pending') {
this.fulfilledQue.push(value => {
try {
const x = onFulfilled(value);
this.resolvePromise(newPromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});

this.rejectedQue.push(reason => {
try {
const x = onRejected(reason);
this.resolvePromise(newPromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}
});

return newPromise;
};

然后我们需要在 Promise 状态发生改变的时候调用onFulfilledonRejected,可以在原来的resolvereject基础上进行补充,这里使用setTimeout模拟异步实现的方式

resolve = value => {
setTimeout(() => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.fulfilledQue.forEach(cb => {
cb(this.value);
});
}
});
};

reject = reason => {
setTimeout(() => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.rejectedQue.forEach(cb => {
cb(this.reason);
});
}
});
};

resolvePromise

resolvePromise的实现相对简单一点,直接按照规范定义一步一步来即可

resolvePromise = (promise, x, resolve, reject) => {
// https://promisesaplus.com/#point-48
// 如果then提供的onFulfilled或者onRejected函数执行返回的值和then返回的是同一个promise,会导致下文this.resolvePromise重复调用,形成死循环
if (promise === x) {
reject(
new TypeError(
'then must return a different promise with fulfilled callback',
),
);
}

// https://promisesaplus.com/#point-49
// 如果是一个 Promise,需要判断其状态,并同步到后续的 Promise
if (x instanceof APromise) {
// https://promisesaplus.com/#point-50
if (x.state === 'pending') {
x.then(
value => {
this.resolvePromise(promise, value, resolve, reject);
},
reason => {
reject(reason);
},
);
} else {
// https://promisesaplus.com/#point-51
x.then(resolve, reject);
}
} else if (typeof x === 'function' || (typeof x === 'object' && x !== null)) {
// https://promisesaplus.com/#point-53
// thenable 函数
let then;
// https://promisesaplus.com/#point-59
// 保证x.then提供的回调函数只会被执行一次
let hasBeenResolved = false;
try {
then = x.then;
// https://promisesaplus.com/#point-56
if (typeof then === 'function') {
then.call(
x,
y => {
if (!hasBeenResolved) {
hasBeenResolved = true;
// https://promisesaplus.com/#point-57
this.resolvePromise(promise, y, resolve, reject);
}
},
r => {
if (!hasBeenResolved) {
hasBeenResolved = true;
reject(r);
}
},
);
} else {
// https://promisesaplus.com/#point-63
resolve(x);
}
} catch (e) {
// https://promisesaplus.com/#point-60
if (!hasBeenResolved) {
reject(e);
}
}
} else {
// https://promisesaplus.com/#point-64
resolve(x);
}
};

测试

使用 promise-aplus 规范提供的promises-tests进行测试,在原有代码上暴露以下入口方法,然后使用 cjs 语法导出即可

/**
* 测试入口
*/
APromise.defer = APromise.deferred = function() {
let dfd = {};
dfd.promise = new APromise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
};

执行promises-aplus-tests,可以看到完美通过 872 个测试用例。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK