“This article has participated in the call for good writing activities, click to view: the back end, the big front end double track submission, 20,000 yuan prize pool waiting for you to challenge!”
In real-world scenarios, it is often possible to have Promise objects that are neither resolve nor reject. For example, the HTTP request is canceled. As we know, JavaScript memory management is based on reference counting, and there is no explicit way to tell promises that “you won’t use them anymore,” so in theory a large number of such promises could lead to memory leaks. But is this the case?
test
In a NodeJS 14.x environment, we tested the memory footprint of Promise.
Promise with no internal callback
Let’s look directly at what happens to the Heap memory after we create a billion Promise objects that are neither resolve nor Reject.
The test script
let used = process.memoryUsage().heapUsed / 1024 / 1024;
console.log('Memory occupied when the program starts:The ${Math.round(used * 100) / 100} MB`);
global.gc();
used = process.memoryUsage().heapUsed / 1024 / 1024;
console.log('GC takes up memory after startup:The ${Math.round(used * 100) / 100} MB`);
for (let i = 0; i < 1000000000; ++i) {
new Promise(rs= > {
if (Math.random() === NaN) { // Construct an impossible condition
rs(); // Never execute here, just to reference rs()
}
}).then(() = > {
// This is not possible
console.log('never resolved')})}; used = process.memoryUsage().heapUsed /1024 / 1024;
console.log('Promise will occupy memory after creation:The ${Math.round(used * 100) / 100} MB`);
global.gc();
used = process.memoryUsage().heapUsed / 1024 / 1024;
console.log('occupies memory after GCThe ${Math.round(used * 100) / 100} MB`);
Copy the code
The results
Memory used when the program is started: 1.99 MB Memory used after GC: 1.78 MB Memory used after Promise creation: 2.34 MB Memory used after GC: 1.77 MBCopy the code
After creating a billion unreleased Promise objects, memory remained essentially unchanged. In the above example, since there is no callback waiting or asynchronous invocation inside the Promise function, it is possible that the JavaScript engine has been optimized to release the Promise automatically.
With this in mind, let’s test again using a scenario with an internal callback waiting.
Call back incomplete promises
The test script
let used = process.memoryUsage().heapUsed / 1024 / 1024;
console.log('Memory occupied when the program starts:The ${Math.round(used * 100) / 100} MB`);
global.gc();
used = process.memoryUsage().heapUsed / 1024 / 1024;
console.log('GC takes up memory after startup:The ${Math.round(used * 100) / 100} MB`);
let rand = Math.random();
let N = 0;
for (let i = 0; i < 1000000; ++i) {
new Promise(rs= > {
setTimeout(() = > {
if (rand === 999) { // Construct an impossible condition
rs(); // Never execute here, just to reference rs()}},86400000); // Wait 24 hours to execute, it will not be completed
++N;
}).then(() = > {
console.log('never resolved')})};setTimeout(() = > {
console.log(N);
used = process.memoryUsage().heapUsed / 1024 / 1024;
console.log('Promise will occupy memory after creation:The ${Math.round(used * 100) / 100} MB`);
global.gc();
used = process.memoryUsage().heapUsed / 1024 / 1024;
console.log('occupies memory after GCThe ${Math.round(used * 100) / 100} MB`);
}, 10000); // Memory will be measured after 10 seconds, and the 24 hour callback will not be completed
Copy the code
The results
Memory used when the program starts: 1.99 MB Memory used after GC starts: 1.78 MB 1000000 Memory used after Promise creation: 522.05 MB Memory used after GC: 521.98 MBCopy the code
As you can see, promises with internal callbacks are memory hogs. And when the internal callback is not complete, the memory is continually suspended, even if the GC does not free it automatically. So what happens to this memory if the callback completes, but still neither resolve nor reject? Continue testing…
Call back the completed Promise
The test script
let used = process.memoryUsage().heapUsed / 1024 / 1024; Console. log(' program startup memory: ${math.round (used * 100) / 100} MB '); global.gc(); used = process.memoryUsage().heapUsed / 1024 / 1024; Console. log(' GC footprint after startup: ${math.round (used * 100) / 100} MB '); let rand = Math.random(); let N = 0; for (let i = 0; i < 1000000; ++i) { new Promise(rs => { setTimeout(() => { ++N; If (rand === 999) {// Construct an impossible condition rs(); }). Then (() => {console.log('never resolved')})}; setTimeout(() => { console.log(N); used = process.memoryUsage().heapUsed / 1024 / 1024; Console. log(' Promise memory: ${math.round (used * 100) / 100} MB '); global.gc(); used = process.memoryUsage().heapUsed / 1024 / 1024; Console. log(' memory occupied after GC ${math.round (used * 100) / 100} MB '); }, 10000); // Wait 10 milliseconds for the callback above, 10 seconds for the callback to completeCopy the code
The results
Memory used when the program starts: 1.99 MB Memory used after GC starts: 1.78 MB 1000000 Memory used after Promise creation: 522.57 MB Memory used after GC 1.8 MBCopy the code
As you can see, any Promise that has a callback inside it will consume memory. However, after the callback is completed, the reference count for this part of memory should be cleared to zero, so it will be freed automatically after GC.
conclusion
- Incomplete execution
Promise
(including internally pending callback incomplete)It takes up memory. - Executed completed
Promise
(including internal waiting callbacks are also executed),Does not take up memory and can be freed by GC. - Executed completed
Promise
Even if it is not triggeredresolve
或reject
Can also beGC
Automatically release. - In summary, there is no need to worry about neither
resolve
Also don’treject
的Promise
Object causes a memory leak.