Before you get to know promises, you need to have a few basics up front
Task queue (message queue)
- A queue is a data structure that holds tasks to be executed
- The types of tasks in the task queue include mouse scrolling, click, move, timer, websocket, file reading and writing, DOM parsing, style calculation, layout calculation, JS execution, and so on
- The above tasks are executed in the main thread. Due to the single thread mechanism of JS, a task can only be executed after all the tasks in front of it are executed. It may take a long time for a single task to execute and occupy the main thread.
- Specific business scenarios: For JS tasks that frequently change DOM elements, different JS interfaces need to be called for each change, resulting in a longer task time. If the task is executed asynchronously, there may be many queued tasks before it is added to the task queue
Therefore, in order to deal with high-priority tasks and solve the problem that the execution time of single-threaded tasks is too long, tasks are divided into macro tasks and micro tasks.
Asynchronous tasks
- When a process executes a task, it takes a while for the task to return, so it is placed in a module dedicated to asynchronous tasks and continues to execute other modules in the task queue to prevent message queues from blocking.
- Common asynchronous tasks: timers, Ajax, event bindings, async await, Promise
Microtasks and macro tasks
- Each task in the task queue is a macro task. In the process of execution, if any microtask is generated, it is added to the microtask queue
Asynchronous requests (such as timeout errors) have many applications
Macro task | Micro tasks |
---|---|
Render event | promise[then/catch/finally] |
request | proxy |
The script code block | MutationObserver |
setTimeout | process.nextTick |
setInterval | QueueMicrotask (for creating microtasks) |
setimemediate/ I/O | async/await |
Note: the next macro task will be executed only after all the microtasks in the current macro task are completed, forming an event loop (execution is to push the task onto the execution stack to perform the corresponding function operation).
Promise term
- A promise is an object or function that has then methods and behaves according to this specification
- Thenable is an object or function that has then methods
- Value is the successful value of the promise state, which is also the resolve parameter. It can be a variety of data types, including undefined/thenable or promise
- Reason is the reject value when the promise state fails
- Exception is an exception thrown with a throw
Promise specification
Promise States
Promises should have three states. Pay attention to the fluid relationship between them.
1. pending
1.1 The initial state can be changed.
1.2 A Promise is in this state before either resolve or Reject.
1.3 Can be fulfilled through the resolve -> depressing state;
1.4 Reject -> Reject;
2. fulfilled
2.1 Final state, immutable.
2.2 A Promise that is resolved becomes this state.
2.3 Must have a value
3. rejected
3.1 Final state, immutable.
3.2 A promise becomes this state when it is rejected
3.3 You must have a Reason
State of the circulation
Tips: To summarize, the promise state flow looks like this
pending -> resolve(value) -> fulfilled
pending -> reject(reason) -> rejected
Implement promises manually
// 2. Define three state types
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
// 1. Initialize class
class MPromise {
FULFILLED_CALLBACK_LIST = [];
REJECTED_CALLBACK_LIST = [];
_status = PENDING; // Private variables
constructor(fn) {
// 3. Set the initial state
this.status = PENDING;
this.value = null;
this.reason = null;
The promise constructor takes arguments * 5.1. This is a function that accepts two arguments, resolve, reject * 5.2.new promise, and any errors are rejected */
try{
// Execute immediately after initialization
fn(this.resolve.bind(this), this.reject.bind(this));
}catch(e){
this.reject(e)
}
}
// 7. Implement get set extra value storage to avoid an infinite loop
get status() {
return this._status;
}
set status(newStatus) {
this._status = newStatus;
switch(newStatus) {
case FULFILLED:
this.FULFILLED_CALLBACK_LIST.forEach(callback= > {
callback(this.value);
})
break;
case REJECTED:
this.REJECTED_CALLBACK_LIST.forEach(callback= > {
callback(this.reason);
})
break; }}// 4. 改 变 status, pending -> pity /rejected; The value, "reason
resolve(value) {
if(this.status === PENDING){
this.value = value;
this.status = FULFILLED; }}reject(reason) {
if(this.status === PENDING){
this.reason = reason;
this.status = REJECTED; }}// implement the then function
then(onFulfilled, onRejected) {
const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) = > value;
const realOnRejected = this.isFunction(onRejected) ? onRejected: (reason) = > {
throw reason;
}
const promise2 = new MPromise((resolve, reject) = > {
/ / 8
const fulfilledMicroTack = () = > {
queueMicrotask(() = > {
try {
const x = realOnFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject)
} catch(e) { reject(e); }})}const rejectedMicroTack = () = > {
queueMicrotask(() = > {
try {
const x = realOnRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
switch(this.status) {
case FULFILLED:
fulfilledMicroTack();
break;
case REJECTED:
rejectedMicroTack()
break;
case PENDING:
this.FULFILLED_CALLBACK_LIST.push(realOnFulfilled);
this.REJECTED_CALLBACK_LIST.push(realOnRejected);
break; }});return promise2;
}
// 10
catch(onRejected) {
return this.then(null, onRejected);
}
/ / 9.
resolvePromise(promise2, x, resolve, reject) {
if(promise2 === x) {
return reject(new TypeError("The promise and the return value are the same"))}if(x instanceof MPromise) {
queueMicrotask(() = > {
x.then((y) = > {
this.resolvePromise(promise2, y, resolve, reject); })})}else if(typeof x==='object' || this.isFunction(x)) {
if(x === null) {
return resolve(x);
}
let then = null;
try {
then = x.then;
} catch (e) {
return reject(e);
}
if(this.isFunction(then)) {
// Limit the number of calls
let called = false;
try {
then.call(x,(y) = > {
if(called) {
return;
}
called = true;
this.resolvePromise(promise2,y,resolve,reject);
},(r) = > {
if(called){
return;
}
called = true; reject(r); })}catch (error) {
if(called) {
return; } reject(error); }}else{ resolve(x); }}else{
resolve(x)
}
}
// Check whether the received argument is a function
isFunction (param) {
return typeof param === 'function'
}
// Static method
static resolve(value){
if(value instanceof MPromise){
return value;
}
return new MPromise((resolve) = >{ resolve(value); })}static reject(reason){
return new MPromise((resolve,reject) = >{ reject(reason); }}})Copy the code
Call Promise to summarize the problem
1. Why is the output value of promise resolve undefined
const test = new MPromise((resolve, reject) = > {
setTimeout(() = > {
resolve(111);
}, 1000);
}).then((value) = > {
console.log('then');
});
setTimeout(() = > {
console.log(test);
}, 3000)
Copy the code
- Because we’re writing it this way, we’re saying return undefined in dot then, so the final value is undefined.
- If you explicitly return a value, it is not undefined; Such as the return value.
2. Why do I print promise in the catch callback and show the state as pending
const test = new MPromise((resolve, reject) = > {
setTimeout(() = > {
reject(111);
}, 1000);
}).catch((reason) = > {
console.log(`[catch] reason=${reason}`);
console.log(test) //status: pending
});
setTimeout(() = > {
console.log(test); //status: fulfilled
}, 3000)
Copy the code
- The catch function returns a new promise, and test is the new promise
- In the catch callback, when the promise is printed, the entire callback is not complete (so the state is pending), and the state changes only when the entire callback is complete
- The callback function of catch, if successfully executed, will change the state of this new Promise to depressing
Implement the promise.all method
Promise.all = (arr) = > {
return new Promise((resolve, reject) = > {
let res = [];
let count = 0;
for (let i = 0; i < arr.length; i++) {
//
Promise.resolve(arr[i]).then((value) = > {
// error 1: do not use res.push(value); Because this is asynchronous, if you use push, the array will be sequential chaos, so use assignment to avoid
res[i] = value;
Res.length === arr.length; If arr[1] is executed first, the length of arr. Length will be 2. Therefore, a record is required to determine whether the record value is equal to arr
count ++;
if(count === arr.length) {
resolve(res);
}
}).catch((reason) = >{ reject(reason); })}})}Copy the code
Realize the promise. Prototype. Finally
Promise.prototype.finally = function(callback) {
return this.then((value) = > {
return Promise.resolve(callback()).then(() = > value);
}),
(err) = > {
return Promise.resolve(callback()).then(() = > {throwerr}); }}Copy the code
To implement the promise.allseettled method, you need to return the status and results of all promises
function PromiseAllSettled(promiseArray) {
return new Promise(function (resolve, reject) {
// Determine the parameter type
if (!Array.isArray(promiseArray)) {
return reject(new TypeError('arguments muse be an array'))}let counter = 0;
const promiseNum = promiseArray.length;
const resolvedArray = [];
for (let i = 0; i < promiseNum; i++) {
Promise.resolve(promiseArray[i])
.then((value) = > {
resolvedArray[i] = {
status: 'fulfilled',
value
};
})
.catch(reason= > {
resolvedArray[i] = {
status: 'rejected',
reason
};
})
.finally(() = > {
counter++;
if (counter == promiseNum) {
resolve(resolvedArray)
}
})
}
})
}
Copy the code