promise Key points

The ‘Promise’ object represents an asynchronous operation with three states: ‘pending’ (in progress), ‘fulfilled’ (successful), and ‘rejected’ (failed)

A Promise must be in one of the following states:

  • pending: An initial state that is neither cashed nor rejected.
  • fulfilled: The operation is fulfilled successfully.
  • rejected: It indicates that the operation has failed.

When a promise is called, it starts with a processing state ** ‘(pending). This means that the called function continues to execute while the promise remains in processing until it is resolved, providing the calling function with whatever data it requests.

The created promise ends up in the ** resolved state ** ‘(fulfilled)’ or the ** rejected state ** ‘(rejected)’, and the corresponding callback function is called on completion (passed to then and catch).

Features:

1.The initial state of the promise status is pending. Once the promise status is changed, it does not change again.

2.The state of the object is not affected by the outside world. Only the result of asynchronous operations can determine the current state, and any other operations can not change this state

shortcoming:

1.’Promise’ cannot be cancelled, as it will be executed immediately upon creation and cannot be cancelled midway.

  1. If you do not set the callback function, ‘Promise’ internally thrown errors will not be reflected externally.

  2. When in the ‘pending’ state, it is impossible to know which stage of the current progress (just started or about to finish).

1.Define the structure

First define a class:

1
2
3
4
5
class myPromise {
constructor(fn) {
fn()
}
}

Then, according to the specification promise constructor, we accept a function as an argument, fn. The two arguments of this function are ‘resolve’ and ‘reject’, which is also a function. The ‘resolve’ function is used to change the status of the promise from ‘pending’ to ‘resolved’ and it is called when the asynchronous operation succeeds. The result of the asynchronous operation is passed as a parameter. The ‘reject’ function changes the status of the ‘Promise’ object from ‘pending’ to ‘rejected’ .It is called when the asynchronous operation fails, and passes the error reported by the asynchronous operation as an argument.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Define the initialization state, the successful value, the failed value, and the array to hold the successful and failed callbacks respectively
class myPromise {
constructor(fn) {
this.value = undefined; //Success value
this.reason = undefined; // Failed value
this.status = "pending"; //Initialize the pending state
this.resolveCallbacks = []; //Store the successful callback array
this.rejectCallbacks = []; //Store failed callback arrays
const resolve = (value) => {
if(this.status === 'pending') {
this.status = 'fulfilled'
this.value = value
this.resolveCallbacks.foreach(item => item(value))
}
}
const reject = (reason) => {
if(this.status === 'pending') {
this.status = 'rejected'
this.reason = reason
this.rejectCallbacks.forach(item => item(reson))
}
}
try {
fn(resolve,reject)
}catch(error) {
reject(error)
}
}

}
the implement of ‘then’ method

A Promise instance has a ‘then’ method, that is, the ‘then’ method is defined on the prototype object ‘promise.prototype’. Its purpose is to add a callback function to a Promise instance when the state changes. As mentioned earlier, the first argument to the ‘then’ method is the ‘resolved’ status callback function, and the second argument is the ‘rejected’ status callback function, both of which are optional.

The ‘then’ method returns a new ‘Promise’ instance (note, not the original Promise instance). Therefore, it can be written in a chain way, that is, the ‘then’ method is followed by another ‘then’ method.

Because ‘then’ is called after the instance is created, we create another class method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// Define the initialization state, the successful value, the failed value, and the array to hold the successful and failed callbacks respectively
class myPromise {
constructor(fn) {
this.value = undefined; //Success value
this.reason = undefined; // Failed value
this.status = "pending"; //Initialize the pending state
this.resolveCallbacks = []; //Store the successful callback array
this.rejectCallbacks = []; //Store failed callback arrays
const resolve = (value) => {
if(this.status === 'pending') {
this.status = 'fulfilled'
this.value = value
/**
* When performing resolve or reject, iterate through its own callbacks array,
* Look in the array and see if there are any functions left over from then,
* Then execute the functions in the array one by one, passing in the corresponding arguments as they are executed
*/
this.resolveCallbacks.foreach(item => item(value))
}
}
const reject = (reason) => {
if(this.status === 'pending') {
this.status = 'rejected'
this.reason = reason
this.rejectCallbacks.forach(item => item(reson))
}
}
try {
fn(resolve,reject)
}catch(error) {
reject(error)
}
}
//then
then(onFulfilled,onRejected) {
// If you pass something other than a function, you need to do compatibility processing
onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : value => value
onRejected = typeof onRejected == 'function' ? onRejected: reason => { throw reason }
// Change the state
return new myPromsie((resolve,reject) => {
if(this.status === 'fullfilled') {
setTimeout(() => {
try {
const value = onFulfilled(this.value)
resolve(value)
}catch(error) {
reject(error)
}
})
}
if(this.status === 'rejected') {
setTimeout(() => {
try {
const reason= onRejected(this.reason)
reject(reason)
}catch(error) {
reject(error)
}
})
}
if(this.status === 'pending') {
this.resolveCallbacks.push(() => {
setTimeout(() => {
try {
const value = onFulfilled(this.value)
resolve(value)
} catch(e) {
reject(e)
}

})
})
this.rejectCallbacks.push(() => {
setTimeout(() => {
try {
const reason = onRejected(this.reason)
reject(reason)
} catch(e) {
reject(e)
}

})
})
}
})
}
}
the implement of ‘resolve’ and ‘reject’ methods

Promise.resolve(value) Converts a given value to a Promise object.

  • If the value is a promise, the promise is returned;
  • If the value is thenable (that is, with a “then” method), the returned promise will “follow “the thenable object, taking its final state;
  • Otherwise, the returned promise will be fulfilled with this value, which is the value at which the ‘resolve()’ method is performed (the status is fulfilled).

Promise.reject()method returns a ‘Promise’ object with a reason for the rejection

1
2
3
4
5
6
7
8
9
10
resolve(value) {
return new myPromise((resolve,reject) => {
resolve(value)
})
}
reject(reson) {
return new myPromise((resolve,reject) => {
reject(reson)
})
}
the implement of ‘catch’ method

The ‘catch()’ method returns a ‘Promise’ and handles the rejection case. It behaves the same as calling ‘Promise.prototype.then(undefined, onRejected)’.

In fact, calling ‘obj.catch(onRejected)’ ‘internal calls’ obj.then(undefined, onRejected)’. (This means that we explicitly use ‘obj.catch(onRejected)’ and internally we actually call ‘obj.then(undefined, onRejected)’)

The ‘Promise.prototype.catch()’ method is an alias for.then(null, rejection) or.then(undefined, rejection), and is used to specify the callback function when an error occurs.

1
2
3
catch (onRejected) {
return this.then(undefined, onRejected)
}
the implement of ‘finally’ method

The ‘finally()’ method is used to specify an operation that will be performed regardless of the final state of the Promise object. This method was introduced as standard by ES2018.

Regardless of the last state of the promise, the callback function specified by the ‘finally’ method will be executed after the callback function specified by ‘then’ or ‘catch’ has been executed

1
2
3
4
5
6
7
8
9
10
11
12
13
finally(callback) {
const p = this.constructor; //Get the constructor
return this.then(
(value) => {
p.resolve(callback()).then(() => value);
},
(reason) => {
p.catch(callback()).then(() => {
throw new Error(reason);
});
}
);
}
the implement of ‘all’ method

The ‘promise.all ()’ method is used to wrap multiple Promise instances into a new Promise instance.

1
const p = Promise.all([p1, p2, p3]);

The ‘promise.all ()’ method takes an array as an argument. ‘p1’, ‘p2’, and ‘p3’ are all Promise instances; if they are not, the ‘promise.resolve’ method, described below, is called first, turning the argument into a Promise instance, and then further processing. In addition, the ‘promise.all ()’ method may not take an array argument, but it must have an Iterator interface, and each member returned must be a Promise instance.

The state of ‘p’ is determined by ‘p1’, ‘p2’, and ‘p3’, and is divided into two cases.

(1)Only the states of ‘p1’, ‘p2’, and ‘p3’ will become ‘fulfilled’, and the state of ‘p’ will become ‘fulfilled’, at which time the return values of ‘p1’, ‘p2’, and ‘p3’ will be composed of an array and passed to the callback function of ‘p’.

(2)As long as one of ‘p1’, ‘p2’, ‘p3’ is’ rejected ‘, the state of ‘p’ becomes’ rejected ‘, and the return value of the first ‘rejected’ instance is passed to the callback function of ‘p’.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
all(promises) {
//Accept an array of promises and return the result if all promise states are successfully changed
const p1 = new MyPromise((resolve, reject) => {
try {
let count = 0;
const result = [];
// If the passed argument is an empty iterable, an already resolved Promise is returned
if (promises.length === 0) {
return resolve(promises);
}
promises.forEach((p) => {
if(p instanceof myPromise) {
myPromise.resolve(p).then(
res => {
count++;
result.push(res);
if (promises.length === count) {
resolve(result);
}
},
reason => {
//If one of the promises passed in fails (rejected), the failed result is
reject(reason)
}
)
} else {
// The non-promise value in the argument is returned as is in the array
count++;
result.push(res)
count === promises.length && resolve(result);
}
});
} catch (error) {
reject(error);
}
});
return p1;
}
the implement of ‘race’ method

Like the ‘all’ method, the ‘promise.race ()’ method wraps multiple Promise instances into a new Promise instance.

1
const p = Promise.race([p1, p2, p3]);

As soon as one instance of ‘p1’, ‘p2’, ‘p3’ changes state first, the state of ‘p’ changes. The return value of the Promise instance that changes first is passed to p’s callback function.

1
2
3
4
5
6
7
8
9
10
race(promises) {
return new myPromise((resolve,reject) => {
for (let i = 0; i < promises.length; i++) {
Promise.resolve(promises[i]).then((data) => {
resolve(data);
return;
}, err=>reject(err));
}
})
}
the implement of ‘allSettled’ method

[ES2020] (https://github.com/tc39/proposal-promise-allSettled) introduced Promise. AllSettled () method, is used to determine whether a group of asynchronous operations are all over (no matter succeed or fail). That is why it is called “Settled,” which contains both “fulfilled” and “rejected.”

The ‘promise.allsettled ()’ method takes an array as an argument, each member of which is a Promise object, and returns a new Promise object. The state of the returned Promise will not change until all the Promise objects in the parameter array have changed (either ‘fulfilled’ or ‘rejected’).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static allSettled(promises) {
return new MyPromise((resolve, reject) => {
let result = [];
let index = 0;
let addIndex = (key, value, status) => {
let obj = {
status,
value
}
result[key] = obj
index++;
if (index === arr.length) {
resolve(result);
}
};
for (let i = 0; i < arr.length; i++) {
let cur = arr[i];
if (cur instanceof MyPromise) {
cur.then(
(value) => {
addIndex(i, value, 'fulfilled')
},
(reason) => {
addIndex(i, reason, 'rejected')
}
);
} else {
addIndex(i, cur, 'fulfilled')
}
}
});
}
the implement of ‘any’ method

ES2021 introduced [Promise. Any () method] (https://github.com/tc39/proposal-promise-any). This method takes a set of Promise instances as an argument and returns it wrapped as a new Promise instance.

As long as one of the parameter instances becomes’ fulfilled ‘, the wrapper instance will become ‘fulfilled’. If all the parameter instances become ‘rejected’, the packaging instances become ‘rejected’.

‘Promise.any()’ is very similar to the ‘promise.race ()’ method, except that ‘promise.any ()’ does not end when a Promise becomes’ rejected ‘. It must wait until all arguments Promise change to the ‘rejected’ state.

The error thrown by ‘Promise.any()’ is an AggregateError instance, and the errors property of the AggregateError instance object is an array containing all the errors of its members.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
any(promises) {
/*As long as one of the parameter instances becomes fulfilled,
The wrapped instance will become fulfilled;
If all the parameter instances are rejected,
The packaging instance becomes rejected
*/
return new Promise((resolve, reject) => {
let hasOneResolved = false
let remaining = promises.length
const errors = []
for (let index in promises) {
promises[index].then(data => {
if (hasOneResolved) return
hasOneResolved = true
resolve(data)
}, err => {
if (hasOneResolved) return
remaining--
errors[index] = err
remaining || reject(errors)
})
}
})
}