0. Preface
Interviewer: "You write a promise."
Me: "I'm sorry to disturb you, bye!"
Now the front end is getting more and more curly, and I can't write by hand Promise
I'm embarrassed to interview (manual dog head.jpg). Although not many people will implement Promise
in their business, but the process of implementing Promise
will make you understand Promise
better, and there will be problems. can be investigated better.
If you are not familiar with Promises, it is recommended to take a look at the MDN documentation first.
Before implementing Promises, I recommend that you read the Promises/A+ specification (Chinese translation: Promise A+ specification ), and I will not introduce related concepts again in this article . It is recommended that you read the original English first, only high school level English knowledge is enough, and you can read the translation if you don’t understand it.
In addition, this article will use ES6 Class to achieve Promise
. In order to facilitate everyone to compare with the Promise/A+ specification, the following order will be written in the order of the specification.
Before the official start, we create a new project with any name, and follow the steps below to initialize:
- Open CMD or VS Code, run
npm init
, initialize the project - Create a new
PromiseImpl.js
file, and all the code implementations that follow are written in this file
Complete code address: ashengtan/promise-aplus-implementing
1. Terminology
In this part, you should just look at the specification directly, and there is nothing to explain.注意value
的描述, value
thenable
(有then
方法的对象或函数) Promise
, which will be reflected in later implementations.
2. Requirements
2.1 State of Promises
One Promise
has three states:
-
pending
: initial state -
fulfilled
: successfully executed -
rejected
: Refused to execute
A Promise
once changed from pending
to fulfilled
or rejected
cannot be changed to any other state of dcc7f19. When fulfilled
, an immutable value needs to be given; similarly, when rejected
, an immutable reason needs to be given.
Based on the above information, we define 3 constants to represent the status of Promise
:
const STATUS_PENDING = 'pending'
const STATUS_FULFILLED = 'fulfilled'
const STATUS_REJECTED = 'rejected'
Next, we first define the basic framework of Promise
, here I use ES6 Class to define:
class PromiseImpl {
constructor() {}
then(onFulfilled, onRejected) {}
}
Here we first recall the basic usage of Promise
:
const promise = new Promise((resolve, reject) => {
// ...do something
resolve(value) // or reject(error)
})
// 多次调用
const p1 = promise.then()
const p2 = promise.then()
const p3 = promise.then()
Well, continue to improve PromiseImpl
, first improve the construction method:
class PromiseImpl {
constructor() {
// `Promise` 当前的状态,初始化时为 `pending`
this.status = STATUS_PENDING
// fulfilled 时的值
this.value = null
// rejected 时的原因
this.reason = null
}
}
In addition, we also need to define two methods for the callback when fulfilled
and rejected
:
class PromiseImpl {
constructor() {
// ...其他代码
// 2.1.2 When `fulfilled`, a `promise`:
// 2.1.2.1 must not transition to any other state.
// 2.1.2.2 must have a value, which must not change.
const _resolve = value => {
// 如果 `value` 是 `Promise`(即嵌套 `Promise`),
// 则需要等待该 `Promise` 执行完成
if (value instanceof PromiseImpl) {
return value.then(
value => _resolve(value),
reason => _reject(reason)
)
}
if (this.status === STATUS_PENDING) {
this.status = STATUS_FULFILLED
this.value = value
}
}
// 2.1.3 When `rejected`, a `promise`:
// 2.1.3.1 must not transition to any other state.
// 2.1.3.2 must have a reason, which must not change.
const _reject = reason => {
if (this.status === STATUS_PENDING) {
this.status = STATUS_REJECTED
this.reason = reason
}
}
}
}
注意, _resolve()
中, value
是Promise
的话( Promise
), Promise
Execution is complete.这点很重要,因为后面的其他API 如Promise.resolve
、 Promise.all
、 Promise.allSettled
等均需要等待嵌套---fcdfc5e444195b2d3a11f3a0f89cebd8 Promise
才results will be returned.
Finally, don't forget that at new Promise()
we need to pass resolve
and reject
to the caller:
class PromiseImpl {
constructor(executor) {
// ...其他代码
try {
executor(_resolve, _reject)
} catch (e) {
_reject(e)
}
}
}
Use trycatch
to wrap executor
, because this part is the caller's code, we can't guarantee that the caller's code will not make mistakes.
2.2 Then method
A Promise
must provide a then
method, which accepts two parameters:
promise.then(onFulfilled, onRejected)
class PromiseImpl {
then(onFulfilled, onRejected) {}
}
2.2.1 onFulfilled
and onRejected
From Specification 2.2.1 we can learn the following information:
-
onFulfilled
andonRejected
are optional parameters - MUST be ignored if
onFulfilled
andonRejected
are not functions
So, we can do it like this:
class PromiseImpl {
then(onFulfilled, onRejected) {
// 2.2.1 Both `onFulfilled` and `onRejected` are optional arguments:
// 2.2.1.1 If `onFulfilled` is not a function, it must be ignored
// 2.2.1.2 If `onRejected` is not a function, it must be ignored
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : () => {}
onRejected = typeof onRejected === 'function' ? onRejected : () => {}
}
}
2.2.2 onFulfilled
Features
From Specification 2.2.2 we can learn the following information:
- If
onFulfilled
is a function, it must be called afterfulfilled
, the first parameter is the value ofpromise
- can only be called once
class PromiseImpl {
then(onFulfilled, onRejected) {
// ...其他代码
// 2.2.2 If `onFulfilled` is a function:
// 2.2.2.1 it must be called after `promise` is fulfilled,
// with promise’s value as its first argument.
// 2.2.2.2 it must not be called before `promise` is fulfilled.
// 2.2.2.3 it must not be called more than once.
if (this.status === STATUS_FULFILLED) {
onFulfilled(this.value)
}
}
}
2.2.3 onRejected
Features
Same as onFulfilled
, but it is called when rejected
, the first parameter is promise
the reason for failure
class PromiseImpl {
then(onFulfilled, onRejected) {
// ...其他代码
// 2.2.3 If onRejected is a function:
// 2.2.3.1 it must be called after promise is rejected,
// with promise’s reason as its first argument.
// 2.2.3.2 it must not be called before promise is rejected.
// 2.2.3.3 it must not be called more than once.
if (this.status === STATUS_REJECTED) {
onRejected(this.reason)
}
}
}
2.2.4 Asynchronous execution
In daily development, we often use Promise
to do some asynchronous operations. Specification 2.2.4 is to specify the problem of asynchronous execution. The specific can be read in conjunction with the comments in the specification. The key is to ensure onFulfilled
onRejected
to execute asynchronously.
It should be pointed out that the specification does not stipulate that Promise
must be implemented with the micro-task
mechanism, so you can use the macro-task
mechanism to implement it. Of course, the current browser uses micro-task
to achieve. Here for convenience, we use setTimeout
(belongs to macro-task
) to achieve.
Therefore, we need to slightly modify the above code:
class PromiseImpl {
then(onFulfilled, onRejected) {
// ...其他代码
// fulfilled
if (this.status === STATUS_FULFILLED) {
setTimeout(() => {
onFulfilled(this.value)
}, 0)
}
// rejected
if (this.status === STATUS_REJECTED) {
setTimeout(() => {
onRejected(this.reason)
}, 0)
}
}
}
2.2.5 onFulfilled
and onRejected
must be called as functions
This has already been implemented above.
2.2.6 then
can be called multiple times
for example:
const promise = new Promise((resolve, reject) => {
// ...do something
resolve(value) // or reject(error)
})
promise.then()
promise.then()
promise.catch()
因此, Promise
fulfilled
rejected
时, onFulfilled
或---546bf528107be179dd18320549a606b4 onRejected
callback. Remember when we first defined resolve
and reject
? Here we need to modify it to ensure that all callbacks are executed:
const invokeArrayFns = (fns, arg) => {
for (let i = 0; i < fns.length; i++) {
fns[i](arg)
}
}
class PromiseImpl {
constructor(executor) {
// ...其他代码
// 用于存放 `fulfilled` 时的回调,一个 `Promise` 对象可以注册多个 `fulfilled` 回调函数
this.onFulfilledCbs = []
// 用于存放 `rejected` 时的回调,一个 `Promise` 对象可以注册多个 `rejected` 回调函数
this.onRejectedCbs = []
const resolve = value => {
if (this.status === STATUS_PENDING) {
this.status = STATUS_FULFILLED
this.value = value
// 2.2.6.1 If/when `promise` is fulfilled,
// all respective `onFulfilled` callbacks must execute
// in the order of their originating calls to `then`.
invokeArrayFns(this.onFulfilledCbs, value)
}
}
const reject = reason => {
if (this.status === STATUS_PENDING) {
this.status = STATUS_REJECTED
this.reason = reason
// 2.2.6.2 If/when `promise` is rejected,
// all respective `onRejected` callbacks must execute
// in the order of their originating calls to `then`.
invokeArrayFns(this.onRejectedCbs, reason)
}
}
}
}
Seeing this, you may have questions, when to store the corresponding callbacks in onFulfilledCbs
and onRejectedCbs
, the answer is when calling then
:
class PromiseImpl {
then(onFulfilled, onRejected) {
// ...其他代码
// pending
if (this.status === STATUS_PENDING) {
this.onFulfilledCbs.push(() => {
setTimeout(() => {
onFulfilled(this.value)
}, 0)
})
this.onRejectedCbs.push(() => {
setTimeout(() => {
onRejected(this.reason)
}, 0)
})
}
}
}
At this time Promise
is in pending
state, it is impossible to determine whether it is fulfilled
or rejected
, so the callback function needs to be stored. After confirmation, execute the corresponding callback function.
Note: invokeArrayFns
comes from the source code in Vue.js 3.
2.2.7 then
must return Promise
promise2 = promise1.then(onFulfilled, onRejected)
So, in our code above, how can ---859c5af24d5b0da835e24684933bf2c9 then
return Promise
? It's simple:
class PromiseImpl {
then(onFulfilled, onRejected) {
let promise2 = new PromiseImpl((resolve, reject) => {
if (this.status === STATUS_FULFILLED) {
// ...相关代码
}
if (this.status === STATUS_REJECTED) {
// ...相关代码
}
if (this.status === STATUS_PENDING) {
// ...相关代码
}
})
return promise2
}
}
Since calling then
then returns a new Promise
object, we can also chain calls:
Promise.resolve(42).then().then()...
2.2.7.1 ~ 2.2.7.4 These four points are more important, let's take a look at them separately.
2.2.7.1如果onFulfilled
onRejected
返回一个值x
, Promise
, [[Resolve]](promise2, x)
。
Explanation: In fact, the so-called operation Promise
the solution process is to perform an operation. We extract this operation into a method and name it: promiseResolutionProcedure(promise, x, resolve, reject)
. For convenience, we transparently transmit resolve
and reject
together.
class PromiseImpl {
then(onFulfilled, onRejected) {
// ...其他代码
let promise2 = new PromiseImpl((resolve, reject) => {
if (this.status === STATUS_FULFILLED) {
setTimeout(() => {
// 2.2.7.1
let x = onFulfilled(this.value)
promiseResolutionProcedure(promise2, x, resolve, reject)
}, 0)
}
if (this.status === STATUS_REJECTED) {
setTimeout(() => {
// 2.2.7.1
let x = onRejected(this.reason)
promiseResolutionProcedure(promise2, x, resolve, reject)
}, 0)
}
if (this.status === STATUS_PENDING) {
this.onFulfilledCbs.push(() => {
setTimeout(() => {
// 2.2.7.1
let x = onFulfilled(this.value)
promiseResolutionProcedure(promise2, x, resolve, reject)
}, 0)
})
this.onRejectedCbs.push(() => {
setTimeout(() => {
// 2.2.7.1
let x = onRejected(this.reason)
promiseResolutionProcedure(promise2, x, resolve, reject)
}, 0)
})
}
})
return promise2
}
}
2.2.7.2如果onFulfilled
onRejected
c2b20314c47a4562304d60ae2e4e3eba---抛出一个异常e
, promise2
rejected
,并返回Reason e
.
解释: onFulfilled
onRejected
trycatch
catch
reject(e)
.
class PromiseImpl {
then(onFulfilled, onRejected) {
// ...其他代码
let promise2 = new PromiseImpl((resolve, reject) => {
if (this.status === STATUS_FULFILLED) {
setTimeout(() => {
try {
// 2.2.7.1
let x = onFulfilled(this.value)
promiseResolutionProcedure(promise2, x, resolve, reject)
} catch (e) {
// 2.2.7.2
reject(e)
}
}, 0)
}
if (this.status === STATUS_REJECTED) {
setTimeout(() => {
try {
// 2.2.7.1
let x = onRejected(this.reason)
promiseResolutionProcedure(promise2, x, resolve, reject)
} catch (e) {
// 2.2.7.2
reject(e)
}
}, 0)
}
if (this.status === STATUS_PENDING) {
this.onFulfilledCbs.push(() => {
setTimeout(() => {
try {
// 2.2.7.1
let x = onFulfilled(this.value)
promiseResolutionProcedure(promise2, x, resolve, reject)
} catch (e) {
// 2.2.7.2
reject(e)
}
}, 0)
})
this.onRejectedCbs.push(() => {
setTimeout(() => {
try {
// 2.2.7.1
let x = onRejected(this.reason)
promiseResolutionProcedure(promise2, x, resolve, reject)
} catch (e) {
// 2.2.7.2
reject(e)
}
}, 0)
})
}
})
// 2.2.7 `then` must return a promise
return promise2
}
}
2.2.7.3 onFulfilled
不是函数且---45bb07f82f5177121def731843f0cd1e promise1
fulfilled
, promise2
fulfilled
返回与promise1
same value.
Explanation: Transparent transmission of values, for example:
Promise.resolve(42).then().then(value => console.log(value)) // 42
In the first then
, we ignore onFulfilled
, then in the chain call, we need to pass the value to the following then
:
class PromiseImpl {
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
// ...其他代码
}
}
2.2.7.4 onRejected
不是promise1
4e0c0b867f32400fa58a0ce0bbeeeb57---已经rejected
, promise2
rejected
返回与promise1
Same reason.
Explanation: In the same way, the reason should also be transparently transmitted:
Promise.reject('reason').catch().catch(reason => console.log(reason)) // 'reason'
class PromiseImpl {
then(onFulfilled, onRejected) {
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
// ...其他代码
}
}
2.3 Promise resolution process
Promise
The solution process is an abstract operation, input a promise
and a value, here we name it promiseResolutionProcedure(promise, x, resolve, reject)
, and pass in resolve
and reject
two methods are used to call when fulfilled
and rejected
respectively.
2.3.1 If promise
and x
are the same object
If promise
and x
are the same object, reject the promise
with reason TypeError
.
const promiseResolutionProcedure = (promise, x, resolve, reject) => {
// 2.3.1 If `promise` and `x` refer to the same object,
// reject `promise` with a `TypeError` as the reason
if (promise === x) {
return reject(new TypeError('`promise` and `x` refer to the same object, see: https://promisesaplus.com/#point-48'))
}
// ...其他代码
}
2.3.2 If x
is a Promise object
If x
is a Promise
object, you need to execute recursively:
const promiseResolutionProcedure = (promise, x, resolve, reject) => {
// ...其他代码
// 2.3.2 If `x` is a promise, adopt its state:
// 2.3.2.1 If `x` is pending, `promise` must remain pending until `x` is fulfilled or rejected.
// 2.3.2.2 If/when `x` is fulfilled, fulfill `promise` with the same value.
// 2.3.2.3 If/when `x` is rejected, reject `promise` with the same reason.
if (x instanceof PromiseImpl) {
return x.then(
value => promiseResolutionProcedure(promise, value, resolve, reject),
reason => reject(reason)
)
}
}
2.3.3 if x
is an object or function
If x
is an object or function:
2.3.3.1 Assign x.then
to x
Explanation: This is done for two purposes:
- Avoid multiple accesses to
x.then
(this is also a small trick in daily development. When accessing the same attribute of an object multiple times, we usually use a variable to store the attribute to avoid multiple prototype chain lookup) - The value of
x.then
may be changed during execution
const promiseResolutionProcedure = (promise, x, resolve, reject) => {
// ...其他代码
// 2.3.3 Otherwise, if x is an object or function
if ((x !== null && typeof x === 'object') || typeof x === 'function') {
// 2.3.3.1 Let `then` be `x.then`
let then = x.then
}
}
2.3.3.2 x.then
fa745974bebd744931344d3889d3f5c5---取值时抛出异常e
, promise
,原因e
const promiseResolutionProcedure = (promise, x, resolve, reject) => {
// ...其他代码
if ((x !== null && typeof x === 'object') || typeof x === 'function') {
try {
// 2.3.3.1 Let `then` be `x.then`
} catch (e) {
// 2.3.3.2 If retrieving the property `x.then` results in a thrown exception `e`,
// reject `promise` with `e` as the reason.
reject(e)
}
}
}
2.3.3.3 if then
is a function
If then
is a function, then x
then
the scope of ---46c6b394ae17662482e7a824139ee62f--- and call then
The first is named resolvePromise
and the second is named rejectPromise
.
Explanation: It means to specify its this
value when calling then
x
, and need to pass in two callback functions at the same time. At this time, it is best to use call()
to achieve:
then.call(x, resolvePromise, rejectPromise)
The meanings of 2.3.3.3.1 ~ 2.3.3.3.4 are summarized as follows:
2.3.3.3.1 If
resolvePromise
is called, recursively callpromiseResolutionProcedure
with valuey
. BecausePromise
can be nested inPromise
:then.call( x, y => promiseResolutionProcedure(promise, y, resolve, reject), rejectPromise )
2.3.3.3.2如果
rejectPromise
,r
,Promise
,原因r
:then.call( x, y => promiseResolutionProcedure(promise, y, resolve, reject), // resolvePromise r => reject(r) // rejectPromise )
2.3.3.3.3 If
resolvePromise
andrejectPromise
are called, or are called multiple times with the same parameter, only the first call is performedExplanation: Here we can solve it by setting a flag bit, and then make judgments in the two callback functions of
resolvePromise
andrejectPromise
respectively:// 初始化时设置为 false let called = false if (called) { return } // `resolvePromise` 和 `rejectPromise` 被调用时设置为 true called = true
- 2.3.3.3.4调用
then
异常e
时,resolvePromise
rejectPromise
,则忽略; Otherwise reject thePromise
with reasone
const promiseResolutionProcedure = (promise, x, resolve, reject) => {
// ...其他代码
let called = false
if ((x !== null && typeof x === 'object') || typeof x === 'function') {
try {
let then = x.then
if (typeof then === 'function') {
// 2.3.3.3 If `then` is a function, call it with `x` as `this`,
// first argument `resolvePromise`, and second argument `rejectPromise`
then.call(
// call it with `x` as `this`
x,
// `resolvePromise`
// 2.3.3.3.1 If/when `resolvePromise` is called with a value `y`,
// run `[[Resolve]](promise, y)`.
y => {
// 2.3.3.3.3 If both `resolvePromise` and `rejectPromise` are called,
// or multiple calls to the same argument are made,
// the first call takes precedence, and any further calls are ignored.
if (called) {
return
}
called = true
promiseResolutionProcedure(promise, y, resolve, reject)
},
// `rejectPromise`
// 2.3.3.3.2 If/when `rejectPromise` is called with a reason `r`,
// reject `promise` with `r`
r => {
// 2.3.3.3.3
if (called) {
return
}
called = true
reject(r)
}
)
} else {
// 2.3.3.4 If `then` is not a function, fulfill `promise` with `x`
resolve(x)
}
} catch (e) {
// 2.3.3.3.3
if (called) {
return
}
called = true
// 2.3.3.2 If retrieving the property `x.then` results in a thrown exception `e`,
// reject `promise` with `e` as the reason.
// 2.3.3.3.4 If calling `then` throws an exception `e`
// 2.3.3.3.4.1 If `resolvePromise` or `rejectPromise` have been called, ignore it
// 2.3.3.3.4.2 Otherwise, reject `promise` with `e` as the reason
reject(e)
}
}
}
2.3.3.4 If then
is not a function, execute this promise
, the parameter is x
const promiseResolutionProcedure = (promise, x, resolve, reject) => {
// ...其他代码
if ((x !== null && typeof x === 'object') || typeof x === 'function') {
try {
let then = x.then
if (typeof then === 'function') {
// 2.3.3.3
} else {
// 2.3.3.4 If `then` is not a function, fulfill `promise` with `x`
resolve(x)
}
} catch (e) {
}
}
}
2.3.4 If x
is neither an object nor a function
If x
is neither an object nor a function, execute this promise
with parameters x
:
const promiseResolutionProcedure = (promise, x, resolve, reject) => {
// ...其他代码
if ((x !== null && typeof x === 'object') || typeof x === 'function') {
// 2.3.3
} else {
// 2.3.4 If `x` is not an object or function, fulfill `promise` with `x`
resolve(x)
}
}
So far, our customization Promise
has been completed. Here is the source code: promise-aplus-implementing .
4. How to test
The Promise/A+ specification provides a test script: promises-tests , which you can use to test that your implementation conforms to the specification.
Add the following code in PromiseImpl.js
:
// PromiseImpl.js
const STATUS_PENDING = 'pending'
const STATUS_FULFILLED = 'fulfilled'
const STATUS_REJECTED = 'rejected'
const invokeArrayFns = (fns, arg) => {
// ...相关代码
}
const promiseResolutionProcedure = (promise, x, resolve, reject) => {
// ...相关代码
}
class PromiseImpl {
// ...相关代码
}
PromiseImpl.defer = PromiseImpl.deferred = () => {
const dfd = {}
dfd.promise = new PromiseImpl((resolve, reject) => {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
module.exports = PromiseImpl
Then add ---8a525d9c53163209cf47d074432e8cc4 package.json
in scripts
:
// package.json
{
"scripts": {
"test": "promises-aplus-tests PromiseImpl.js"
}
}
Then, run npm run test
to execute the test script, if your implementation conforms to the Promise/A+ specification, all test cases will pass.
5. Implementation of other APIs
The Promise/A+ specification only specifies the implementation of the then()
method. Other methods such as catch()
and finally()
are not included in the specification. But in terms of implementation, these methods can be implemented by secondary encapsulation of the then()
method or other methods.
In addition, methods such as all()
, race()
and so on, the parameter is an iterable object, such as Array
, Set
Map
etc. So, what is an iterable object According to the specification in ES6, to be an iterable object, an object must implement the @@iterator
method, that is, the object must have a property named @@iterator
, Usually we use the constant Symbol.iterator to access this property.
According to the above information, to determine whether a parameter is an iterable object, the implementation is as follows:
const isIterable = value => !!value && typeof value[Symbol.iterator] === 'function'
Promise.resolve
Promise.resolve(value)
, static method , its parameters are as follows:
- The parameter is
Promise
object - Arguments are
thenable
object (object withthen()
method) - The argument is a primitive value or an object without a
then()
method - parameter is empty
Therefore, its return value is determined by its parameters: it may be a specific value, or it may be a Promise
object:
class PromiseImpl {
static resolve(value) {
// 1. 参数是 Promise 对象
if (value instanceof PromiseImpl) {
return value
}
// 2. 参数是 thenable
if (value !== null && typeof value === 'object' && typeof value.then === 'function') {
return new PromiseImpl((resolve, reject) => {
value.then(
v => resolve(v),
e => reject(e)
)
})
}
// 3. 参数是原始值或不具有 `then()` 方法的对象
// 4. 参数为空
return new PromiseImpl((resolve, reject) => resolve(value))
}
}
Promise.reject
Promise.reject(reason)
, static method , the parameter is Promise
the reason for refusing to execute, and returns a Promise
object, the state is rejected
class PromiseImpl {
static reject(reason) {
return new PromiseImpl((resolve, reject) => reject(reason))
}
}
Promise.prototype.catch
Promise.prototype.catch(onRejected)
is actually the syntactic sugar of then(null, onRejected)
:
class PromiseImpl {
catch(onRejected) {
return this.then(null, onRejected)
}
}
Promise.prototype.finally
As the name suggests, no matter Promise
the final result is fulfilled
or rejected
, finally
will be executed.
class PromiseImpl {
finally(onFinally) {
return this.then(
value => PromiseImpl.resolve(onFinally()).then(() => value),
reason => PromiseImpl.resolve(onFinally()).then(() => { throw reason })
)
}
}
Promise.all
Promise.all(iterable)
, static method , the parameter is an iterable object:
- Only when all
iterable
inPromise
are successfully executed, willfulfilled
, the return value of the callback function is allPromise
The formed array, in the same order asiterable
. - Once there is a
Promise
that refuses to execute, the status isrejected
, and the reason for the firstPromise
0a77bf93e218d3dd4565989a7c5ccef8--- that refuses to execute is used as the return value of the callback function. - This method returns a
Promise
.
class PromiseImpl {
static all(iterable) {
if (!isIterable(iterable)) {
return new TypeError(`TypeError: ${typeof iterable} is not iterable (cannot read property Symbol(Symbol.iterator))`)
}
return new PromiseImpl((resolve, reject) => {
// `fulfilled` 的 Promise 数量
let fulfilledCount = 0
// 收集 Promise `fulfilled` 时的值
const res = []
for (let i = 0; i < iterable.length; i++) {
const iterator = iterable[i]
iterator.then(
value => {
res[i] = value
fulfilledCount++
if (fulfilledCount === iterable.length) {
resolve(res)
}
},
reason => reject(reason)
)
}
})
}
}
have a test:
const promise1 = Promise.resolve(42)
const promise2 = new PromiseImpl((resolve, reject) => setTimeout(() => resolve('value2'), 1000))
PromiseImpl.all([
promise1,
promise2
]).then(values => console.log('values:', values))
result:
values: [42, 'value2']
It seems perfect, but is it really so? Looking closely at our code, what if iterable
is an empty array? What if iterable
contains Promise
? like this:
PromiseImpl.all([])
PromiseImpl.all([promise1, promise2, 'value3'])
In this case, executing the previous code will not get any result. Therefore, the code needs to be improved again:
class PromiseImpl {
static all(iterable) {
if (!isIterable(iterable)) {
return new TypeError(`TypeError: ${typeof iterable} is not iterable (cannot read property Symbol(Symbol.iterator))`)
}
return new PromiseImpl((resolve, reject) => {
// `fulfilled` 的 Promise 数量
let fulfilledCount = 0
// 收集 Promise `fulfilled` 时的值
const res = []
// - 填充 `res` 的值
// - 增加 `fulfilledCount`
// - 判断所有 `Promise` 是否已经全部成功执行
const processRes = (index, value) => {
res[index] = value
fulfilledCount++
if (fulfilledCount === iterable.length) {
resolve(res)
}
}
if (iterable.length === 0) {
resolve(res)
} else {
for (let i = 0; i < iterable.length; i++) {
const iterator = iterable[i]
if (iterator && typeof iterator.then === 'function') {
iterator.then(
value => processRes(i, value),
reason => reject(reason)
)
} else {
processRes(i, iterator)
}
}
}
})
}
}
Now try again:
const promise1 = PromiseImpl.resolve(42)
const promise2 = 3
const promise3 = new PromiseImpl((resolve, reject) => setTimeout(() => resolve('value3'), 1000))
PromiseImpl.all([
promise1,
promise2,
promise3,
'a'
]).then(values => console.log('values:', values))
// 结果:values: [42, 3, 'value3', 'a']
PromiseImpl.all([]).then(values => console.log('values:', values))
// 结果:values: []
Promise.allSettled
Promise.allSettled(iterable)
, static method , the parameter is an iterable object:
- When all
iterable
inPromise
are successfully executed or rejected, the return value is an object array. - If there is nesting
Promise
, you need to wait for thatPromise
to complete. - Returns a new
Promise
object.
For allSettled
, we can encapsulate it on the basis of all
:
class PromiseImpl {
static allSettled(iterable) {
if (!isIterable(iterable)) {
return new TypeError(`TypeError: ${typeof iterable} is not iterable (cannot read property Symbol(Symbol.iterator))`)
}
const promises = []
for (const iterator of iterable) {
promises.push(
PromiseImpl.resolve(iterator).then(
value => ({ status: STATUS_FULFILLED, value }),
reason => ({ status: STATUS_REJECTED, reason })
)
)
}
return PromiseImpl.all(promises)
}
}
Test Results:
PromiseImpl.allSettled([
PromiseImpl.resolve(42),
PromiseImpl.reject('Oops!'),
PromiseImpl.resolve(PromiseImpl.resolve(4242)),
'a'
]).then(values => console.log(values))
// 结果:
// [
// { status: 'fulfilled', value: 42 },
// { status: 'rejected', reason: 'Oops!' },
// { status: 'fulfilled', value: 4242 },
// { status: 'fulfilled', value: 'a' }
// ]
Promise.race
Promise.race(iterable)
, static method , the parameter is an iterable object:
- When any one of
iterable
Promise
successfully executes or refuses to execute, use thisPromise
the value returned successfully or the reason for refusal to execute - If
iterable
is empty, it is inpending
state - Returns a new
Promise
object
E.g:
Promise.race([
Promise.resolve(42),
Promise.reject('Oops!'),
'a'
]).then(values => console.log(values))
.catch(reason => console.log(reason))
// 结果:42
Promise.race([
Promise.reject('Oops!'),
Promise.resolve(42),
'a'
]).then(values => console.log(values))
.catch(reason => console.log(reason))
// 结果:Oops!
The implementation is as follows:
class PromiseImpl {
static race(iterable) {
if (!isIterable(iterable)) {
return new TypeError(`TypeError: ${typeof iterable} is not iterable (cannot read property Symbol(Symbol.iterator))`)
}
return new PromiseImpl((resolve, reject) => {
if (iterable.length === 0) {
return
} else {
for (let i = 0; i < iterable.length; i++) {
const iterator = iterable[i]
if (iterator && typeof iterator.then === 'function') {
iterator.then(
value => resolve(value),
reason => reject(reason)
)
return
} else {
resolve(iterator)
return
}
}
}
})
}
}
One thing to note here: don't forget to use return
to end the for
loop .
Test Results:
PromiseImpl.race([
PromiseImpl.resolve(42),
PromiseImpl.reject('Oops!'),
'a'
]).then(values => console.log(values))
.catch(reason => console.log(reason))
// 结果:42
PromiseImpl.race([
PromiseImpl.reject('Oops!'),
PromiseImpl.resolve(42),
'a'
]).then(values => console.log(values))
.catch(reason => console.log(reason))
// 结果:'Oops!'
PromiseImpl.race([
'a',
PromiseImpl.reject('Oops!'),
PromiseImpl.resolve(42)
]).then(values => console.log(values))
.catch(reason => console.log(reason))
// 结果:'a'
6. Discuss together
- In 2.3.3.1 assigning
x.then
tothen
, under what circumstances will the pointer ofx.then
be changed? - In 2.3.3.3 if
then
is a function , is there any other way to achieve it besides usingcall()
?
7. Summary
The realization Promise
is basically divided into three steps:
- Define the state of
Promise
- Implement
then
method - Realization
Promise
solving process
8. Write at the end
In the past, I was concerned about whether I could implement a Promise
, and I looked for articles everywhere, this code Ctrl+C
, that code Ctrl+V
. Now, what I value is the implementation process, in which you will not only become more familiar with Promise
but also learn how to turn the specification into actual code step by step. Doing the right thing is far more important than doing the right thing .
If you think this article is helpful to you, please: like , favorite, forward ; if you have any questions, please write it in the comment area, and we will discuss it together.
renew
Thanks for the reminder, the implementation of Promise.resolve and Promise.allSettled is wrong, and the article has been updated.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。