The main technical points
- Chained method calls
- Status mechanism Management
- Prototype method call
- Task queue scheduling
Code implementation
Keep your mouth shut and start writing.
The simplest peomise
To use a Promise, you must new a Promise object and then perform a successful callback
new Promise(function (resolve, reject) {
resolve(1)
}).then(res= > {
console.log('success' + res);
}, err= > {
console.log('failure' + err);
});
Copy the code
So promise is a constructor and contains a prototype method for THEN.
function PromiseA(callback) {
// 1. Verify parameters
if(! callback)throw new Error('callback is required! ');
var self = this;
// 2. State Pendding, resolved, Rejected
this.status = 'pendding';
// 4.2 Success or Failure Data needs to be stored
this.resolveData = undefined;
this.rejectData = undefined;
// 4.1 Callback requires two parameters, defined here
function resolve(data) {
self.resolveData = data;
self.status = 'resolved';
}
function reject(data) {
self.rejectData = data;
self.status = 'rejected';
}
// 3. Execute the promise. Note that the synchronization code may report an error, and you need to actively catch the error
try {
callback(resolve, reject)
} catch(err) {
reject(err)
}
}
// 5. Prototype mount then method
PromiseA.prototype.then = function (resolve, reject) {
if (this.status === 'resolved') {
resolve(this.resolveData)
} else {
reject(this.rejectData)
}
}
// Test case 1
new PromiseA(function (resolve, reject) {
reject(1)
}).then(res= > {
console.log('success' + res);
}, err= > {
console.log('failure' + err);
});
Copy the code
This implements a simple Promise, but it fails when wrapped with a setTimeout.
Added logic to support setTimeout
Since setTimeout is a macro task, it needs to wait for the completion of the execution to execute the callback of THEN. Therefore, it needs to add an execution queue and invoke THEN after the completion of execution
function PromiseA(callback) {
// 1. Verify parameters
if(! callback)throw new Error('callback is required! ');
var self = this;
// 2. State Pendding, resolved, Rejected
this.status = 'pendding';
// 4.2 Success or Failure Data needs to be stored
this.resolveData = undefined;
this.rejectData = undefined;
// 6.2 Replenishes the queue field
this.queues = [];
// 4.1 Callback requires two parameters, defined here
function resolve(data) {
self.resolveData = data;
self.status = 'resolved';
// 6.3 Add queue logic
if (self.queues.length) {
self.queues.forEach(fn= >fn()); }}function reject(data) {
self.rejectData = data;
self.status = 'rejected';
// 6.4 Add queue logic
if (self.queues.length) {
self.queues.forEach(fn= >fn()); }}// 3. Execute the promise. Note that the synchronization code may report an error, and you need to actively catch the error
try {
callback(resolve, reject)
} catch(err) {
reject(err)
}
}
// 5. Prototype mount then method
PromiseA.prototype.then = function (resolve, reject) {
const self = this;
// 6. Add pendding status judgment
if (this.status === 'pendding') {
this.queues.push(function () {
if (self.status === 'resolved') {
resolve(self.resolveData)
} else {
reject(self.rejectData)
}
})
} else if (this.status === 'resolved') {
resolve(this.resolveData)
} else {
reject(this.rejectData)
}
}
// Test case 1
new PromiseA(function (resolve, reject) {
reject(1)
}).then(res= > {
console.log('success' + res);
}, err= > {
console.log('failure' + err);
});
// Test case 2
new PromiseA(function (resolve, reject) {
setTimeout(function (){
reject(2)},1000)
}).then(res= > {
console.log('success' + res);
}, err= > {
console.log('failure' + err);
});
Copy the code
The code is copied in full, just focus on test case 2 and Step 6. Historical test cases are reserved to ensure that historical code is not affected by modification of PromiseA.
In practice, there are catches as well as then, and exceptions are often handled by catch.
Add catch and perfect logic
In the use of Promise, catch calls the corresponding chain, indicating that catch is a prototype method, focusing on how to skip the then chain to call the chain of catch
function PromiseA(callback) {
// 1. Verify parameters
if(! callback)throw new Error('callback is required! ');
var self = this;
// 2. State Pendding, resolved, Rejected
this.status = 'pendding';
// 4.2 Success or Failure Data needs to be stored
this.resolveData = undefined;
this.rejectData = undefined;
// 6.2 Replenishes the queue field
this.queues = [];
// 4.1 Callback requires two parameters, defined here
function resolve(data) {
self.resolveData = data;
self.status = 'resolved';
// 6.3 Add queue logic
if (self.queues.length) {
self.queues.forEach(fn= >fn()); }}function reject(data) {
self.rejectData = data;
self.status = 'rejected';
// 6.4 Add queue logic
if (self.queues.length) {
self.queues.forEach(fn= >fn()); }}// 3. Execute the promise. Note that the synchronization code may report an error, and you need to actively catch the error
try {
callback(resolve, reject)
} catch(err) {
reject(err)
}
}
// 5. Prototype mount then method
PromiseA.prototype.then = function (resolve, reject) {
const self = this;
// 6. Add pendding status judgment
if (this.status === 'pendding') {
this.queues.push(function () {
if (self.status === 'resolved') {
resolve(self.resolveData)
} else {
reject(self.rejectData)
}
})
} else if (this.status === 'resolved') {
resolve(this.resolveData)
} else if (this.status === 'rejected' && reject) {
// 1
reject(this.rejectData)
} else {
// 7.2 Chain calls need to export themselves
return this; }}// 7.3 Supplement catch prototype method
PromiseA.prototype.catch = function (reject) {
reject(this.rejectData)
}
// Test case 1
new PromiseA(function (resolve, reject) {
reject(1)
}).then(res= > {
console.log('success' + res);
}, err= > {
console.log('failure' + err);
});
// Test case 2
new PromiseA(function (resolve, reject) {
setTimeout(function (){
reject(2)},1000)
}).then(res= > {
console.log('success' + res);
}, err= > {
console.log('failure' + err);
});
// Test case 3
new PromiseA(function (resolve, reject) {
reject(3)
}).then(res= > {
console.log('success' + res);
}).catch(err= > {
console.log('failure' + err);
});
Copy the code
Copy all the code, just focus on test case 3 and Step 7. Historical test cases are reserved to ensure that historical code is not affected by modification of PromiseA.
Test case 3 executes successfully, exporting this to resolve the chain problem without affecting history.
Improved catch, fixed chain calls
However, an error occurs after setTimeout is added. It is necessary to solve the problem of calling then or catch after setTimeout task
function PromiseA(callback) {
// 1. Verify parameters
if(! callback)throw new Error('callback is required! ');
var self = this;
// 2. State Pendding, resolved, Rejected
this.status = 'pendding';
// 4.2 Success or Failure Data needs to be stored
this.resolveData = undefined;
this.rejectData = undefined;
// 6.2 Replenishes the queue field
this.queues = [];
// 8.4 Replenish the exception queue
this.rejectedQueues = [];
// 4.1 Callback requires two parameters, defined here
function resolve(data) {
self.resolveData = data;
self.status = 'resolved';
// 6.3 Add queue logic
if (self.queues.length) {
self.queues.forEach(fn= >fn()); }}function reject(data) {
self.rejectData = data;
self.status = 'rejected';
// 6.4 Add queue logic
if (self.queues.length) {
self.queues.forEach(fn= > fn());
}
8.5 Add exception queue logic
if (self.rejectedQueues.length) {
self.rejectedQueues.forEach(fn= > fn());
} else if (self.queues) {
for (var i = 0; i < self.queues.length; i++) {
varfunc = self.queues[i].bind(self); func(); }}}// 3. Execute the promise. Note that the synchronization code may report an error, and you need to actively catch the error
try {
callback(resolve, reject)
} catch(err) {
reject(err)
}
}
// 5. Prototype mount then method
PromiseA.prototype.then = function (resolve, reject) {
const self = this;
// 6. Add pendding status judgment
if (this.status === 'pendding') {
this.queues.push(function () {
if (self.status === 'resolved') {
resolve(self.resolveData)
} else if (self.status === 'rejected' && reject) {
// 8.1 Improve queue judgment
reject(self.rejectData)
}
})
return this;
} else if (this.status === 'resolved') {
resolve(this.resolveData)
} else if (this.status === 'rejected' && reject) {
// 1
reject(this.rejectData)
} else {
// 7.2 Chain calls need to export themselves
return this; }}// 7.3 Supplement catch prototype method
PromiseA.prototype.catch = function (reject) {
const self = this;
// 8.2 Improve exception judgment
if (this.status === 'pendding') {
// 8.3 Replenish the exception queue
this.rejectedQueues.push(function () {
if (self.status === 'rejected' && reject) {
reject(self.rejectData)
}
})
} else if (this.status === 'rejected') {
reject(this.rejectData)
}
}
// Test case 1
new PromiseA(function (resolve, reject) {
reject(1)
}).then(res= > {
console.log('success' + res);
}, err= > {
console.log('failure' + err);
});
// Test case 2
new PromiseA(function (resolve, reject) {
setTimeout(function (){
reject(2)},1000)
}).then(res= > {
console.log('success' + res);
}, err= > {
console.log('failure' + err);
});
// Test case 3
new PromiseA(function (resolve, reject) {
reject(3)
}).then(res= > {
console.log('success' + res);
}).catch(err= > {
console.log('failure' + err);
});
// Test case 4
new PromiseA(function (resolve, reject) {
setTimeout(function (){
reject(4)},1000)
}).then(res= > {
console.log('success' + res);
}).catch(err= > {
console.log('failure' + err);
});
Copy the code
The code is copied in full, just focus on test case 4 and Step 8. Historical test cases are reserved to ensure that historical code is not affected by modification of PromiseA.
At this point, only one layer of chain calls is implemented, and the promise requires multiple layers of chain calls
Multilayer chain perfection
Continue to supplement multi-layer chain logic, compatible with a variety of situations
function PromiseA(callback) {
// 1. Verify parameters
if(! callback)throw new Error('callback is required! ');
var self = this;
// 2. State Pendding, resolved, Rejected
this.status = 'pendding';
// 4.2 Success or Failure Data needs to be stored
this.resolveData = undefined;
this.rejectData = undefined;
// 6.2 Replenishes the queue field
this.queues = [];
// 8.4 Replenish the exception queue
this.rejectedQueues = [];
// 4.1 Callback requires two parameters, defined here
function resolve(data) {
self.resolveData = data;
self.status = 'resolved';
// 6.3 Add queue logic
if (self.queues.length) {
self.queues.forEach(fn= >fn()); }}function reject(data) {
self.rejectData = data;
self.status = 'rejected';
// 6.4 Add queue logic
if (self.queues.length) {
self.queues.forEach(fn= > fn());
}
8.5 Add exception queue logic
if (self.rejectedQueues.length) {
self.rejectedQueues.forEach(fn= >fn()); }}// 3. Execute the promise. Note that the synchronization code may report an error, and you need to actively catch the error
try {
callback(resolve, reject)
} catch(err) {
reject(err)
}
}
// 5. Prototype mount then method
PromiseA.prototype.then = function (resolve, reject) {
const self = this;
// 6. Add pendding status judgment
if (this.status === 'pendding') {
this.queues.push(function () {
if (self.status === 'resolved') {
// 9.1 resolve will return a new Promise, which is accepted here
var result = resolve(self.resolveData)
if (result) {
Check the execution status of a promise in resolve
if (result.status === 'resolved') {
// 9.3 If successful, modify the status data to enter the next chain
self.status = 'resolved';
self.resolveData = result.resolveData;
} else {
// 9.4 If an exception occurs, check whether there is an exception queue and execute the exception queue method
self.status = 'rejected';
self.rejectData = result.rejectData;
if (self.rejectedQueues.length) {
self.rejectedQueues.forEach(fn= >fn()); }}}}else if (self.status === 'rejected' && reject) {
// 8.1 Improve queue judgment
reject(self.rejectData)
}
})
return this;
} else if (this.status === 'resolved') {
resolve(this.resolveData)
} else if (this.status === 'rejected' && reject) {
// 1
reject(this.rejectData)
} else {
// 7.2 Chain calls need to export themselves
return this; }}// 7.3 Supplement catch prototype method
PromiseA.prototype.catch = function (reject) {
const self = this;
// 8.2 Improve exception judgment
if (this.status === 'pendding') {
// 8.3 Replenish the exception queue
this.rejectedQueues.push(function () {
if (self.status === 'rejected' && reject) {
reject(self.rejectData)
}
})
} else if (this.status === 'rejected') {
reject(this.rejectData)
}
}
// Test case 1
new PromiseA(function (resolve, reject) {
reject(1)
}).then(res= > {
console.log('success' + res);
}, err= > {
console.log('failure' + err);
});
// Test case 2
new PromiseA(function (resolve, reject) {
setTimeout(function (){
reject(2)},1000)
}).then(res= > {
console.log('success' + res);
}, err= > {
console.log('failure' + err);
});
// Test case 3
new PromiseA(function (resolve, reject) {
reject(3)
}).then(res= > {
console.log('success' + res);
}).catch(err= > {
console.log('failure' + err);
});
// Test case 4
new PromiseA(function (resolve, reject) {
setTimeout(function (){
reject(4)},1000)
}).then(res= > {
console.log('success' + res);
}).catch(err= > {
console.log('failure' + err);
});
// Test case 5
new PromiseA(function (resolve, reject) {
setTimeout(function (){
resolve(5)},1000)
}).then(res= > {
console.log('success' + res);
return new PromiseA((resolve, reject) = > {
resolve(6)
})
}).then(res= > {
console.log('success' + res);
return new PromiseA((resolve, reject) = > {
reject(7)
})
}).then(res= > {
console.log('success' + res);
}).catch(err= > {
console.log('failure' + err);
});
Copy the code
Copy all the code, just focus on test case 5 and Step 9. Historical test cases are reserved to ensure that historical code is not affected by modification of PromiseA.
Add setTimeout to fix chain macro task failure
When a setTimeout is encountered, the chain calls fail and the output is chaotic. The above example perfected the chain, but since it was a synchronous operation, there was no problem. After adding setTimeout, there was a problem.
The main reason is the instance pointing problem, which requires some logic about this(or instance) pointing.
function PromiseA(callback) {
// 1. Verify parameters
if(! callback)throw new Error('callback is required! ');
var self = this;
// 2. State Pendding, resolved, Rejected
this.status = 'pendding';
// 4.2 Success or Failure Data needs to be stored
this.resolveData = undefined;
this.rejectData = undefined;
// 6.2 Replenishes the queue field
this.queues = [];
// 8.4 Replenish the exception queue
this.rejectedQueues = [];
// 4.1 Callback requires two parameters, defined here
function resolve(data) {
self.resolveData = data;
self.status = 'resolved';
// 6.3 Add queue logic
if (self.queues.length) {
// 10.1 Add the bind method to change this to point to a specific instance
for (var i = 0; i < self.queues.length; i++) {
varfunc = self.queues[i].bind(self); func(); }}}function reject(data) {
self.rejectData = data;
self.status = 'rejected';
8.5 Add exception queue logic
if (self.rejectedQueues.length) {
// add the bind method to change this to refer to a specific instance
for (var i = 0; i < self.rejectedQueues.length; i++) {
varfunc = self.rejectedQueues[i].bind(self); func(); }}else if (self.queues) {
for (var i = 0; i < self.queues.length; i++) {
varfunc = self.queues[i].bind(self); func(); }}}// 3. Execute the promise. Note that the synchronization code may report an error, and you need to actively catch the error
try {
callback(resolve, reject)
} catch(err) {
reject(err)
}
}
// 5. Prototype mount then method
PromiseA.prototype.then = function (resolve, reject) {
// 6. Add pendding status judgment
if (this.status === 'pendding') {
this.queues.push(function () {
// Delete self. This refers to the instance of the current function
var self = this;
if (self.status === 'resolved') {
// 9.1 resolve will return a new Promise, which is accepted here
var result = resolve(self.resolveData)
if (result) {
// 10.4 Resolve After the resolve command is executed, change the state to Pendding to prevent subsequent THEN output
self.status = 'pendding';
// 10.5 Inherits the parent callback and deletes the parent callback to prevent further calls
result.queues = self.queues.slice(1)
self.queues.splice(1, self.queues.length)
result.rejectedQueues = self.rejectedQueues
Check the execution status of a promise in resolve
if (result.status === 'resolved') {
// 9.3 If successful, modify the status data to enter the next chain
self.status = 'resolved';
self.resolveData = result.resolveData;
} else if (result.status === 'rejected') {
// 9.4 If an exception occurs, check whether there is an exception queue and execute the exception queue method
self.status = 'rejected';
self.rejectData = result.rejectData;
if (self.rejectedQueues.length) {
self.rejectedQueues.forEach(fn= >fn.bind(result)()); }}}}else if (self.status === 'rejected' && reject) {
// 8.1 Improve queue judgment
reject(self.rejectData)
}
10.6 Execute the next Promise queue
if (result && result.queues) {
for (var i = 0; i < result.queues.length; i++) {
varfunc = result.queues[i].bind(self); func(); }}})return this;
} else if (this.status === 'resolved') {
resolve(this.resolveData)
} else if (this.status === 'rejected' && reject) {
// 1
reject(this.rejectData)
} else {
// 7.2 Chain calls need to export themselves
return this; }}// 7.3 Supplement catch prototype method
PromiseA.prototype.catch = function (reject) {
// 8.2 Improve exception judgment
if (this.status === 'pendding') {
// 8.3 Replenish the exception queue
this.rejectedQueues.push(function () {
// 10.7 Delete self from the original definition, the current function this refers to the instance, to prevent confusion with the outer definition of self
var self = this;
if (self.status === 'rejected' && reject) {
reject(self.rejectData)
}
})
} else if (this.status === 'rejected') {
reject(this.rejectData)
}
}
// Test case 1
new PromiseA(function (resolve, reject) {
reject(1)
}).then(res= > {
console.log('success' + res);
}, err= > {
console.log('failure' + err);
});
// Test case 2
new PromiseA(function (resolve, reject) {
setTimeout(function (){
reject(2)},1000)
}).then(res= > {
console.log('success' + res);
}, err= > {
console.log('failure' + err);
});
// Test case 3
new PromiseA(function (resolve, reject) {
reject(3)
}).then(res= > {
console.log('success' + res);
}).catch(err= > {
console.log('failure' + err);
});
// Test case 4
new PromiseA(function (resolve, reject) {
setTimeout(function (){
reject(4)},1000)
}).then(res= > {
console.log('success' + res);
}).catch(err= > {
console.log('failure' + err);
});
// Test case 5
new PromiseA(function (resolve, reject) {
setTimeout(function (){
resolve(5)},1000)
}).then(res= > {
console.log('success' + res);
return new PromiseA((resolve, reject) = > {
resolve(6)
})
}).then(res= > {
console.log('success' + res);
return new PromiseA((resolve, reject) = > {
reject(7)
})
}).then(res= > {
console.log('success' + res);
}).catch(err= > {
console.log('failure' + err);
});
// Test case 6
new PromiseA(function (resolve, reject) {
setTimeout(function (){
resolve(8)},1000)
}).then(res= > {
console.log('success' + res);
return new PromiseA((resolve, reject) = > {
setTimeout(function (){
resolve(9)},1000)
})
}).then(res= > {
console.log('success' + res);
return new PromiseA((resolve, reject) = > {
reject(10)
})
}).then(res= > {
console.log('success' + res);
}).catch(err= > {
console.log('failure' + err);
});
Copy the code
The code is copied in full, just focus on test case 6 and Step 10. Historical test cases are reserved to ensure that historical code is not affected by modification of PromiseA.
conclusion
The last piece of code above is the JS implementation of Promise, and the test coverage is not very comprehensive, so there may be other problems.
Implementing Promise with JS is primarily about deepening the technical aspects of native JS such as microtasks, queues, this Pointers, constructors, prototypes, and so on.
Those who are interested can try it for themselves