Resource Management
The @scaryterry/pdfium library uses native WASM resources that must be explicitly released. This guide explains the resource management patterns and best practices.
Why Resource Management Matters
Section titled “Why Resource Management Matters”PDFium allocates resources in WASM memory (outside JavaScript’s garbage collector):
- Document handles — Loaded PDF file data
- Page handles — Rendered page content
- Bitmap handles — Pixel buffers for rendering
- Text page handles — Text extraction state
- Search handles — Text search context
If these resources aren’t released, they accumulate and eventually exhaust available memory.
The using Keyword (Recommended)
Section titled “The using Keyword (Recommended)”ES2024 introduced explicit resource management with the using keyword. This is the recommended approach:
import { PDFium } from '@scaryterry/pdfium';
async function processDocument(data: Uint8Array) { using pdfium = await PDFium.init(); using document = await pdfium.openDocument(data); using page = document.getPage(0);
const text = page.getText(); return text;} // All resources automatically disposed hereHow using Works
Section titled “How using Works”The using keyword:
- Stores the value in a variable
- Calls
[Symbol.dispose]()when the variable goes out of scope - Works with
try/finallysemantics (disposal happens even if errors occur)
// This:{ using page = document.getPage(0); // use page...}
// Is equivalent to:{ const page = document.getPage(0); try { // use page... } finally { page[Symbol.dispose](); }}TypeScript Configuration
Section titled “TypeScript Configuration”To use using, configure TypeScript:
{ "compilerOptions": { "target": "ES2022", "lib": ["ES2022", "DOM", "DOM.Iterable"] }}Or with newer targets:
{ "compilerOptions": { "target": "ES2024" }}Manual Disposal
Section titled “Manual Disposal”If you can’t use the using keyword, call dispose() explicitly:
const pdfium = await PDFium.init();const document = await pdfium.openDocument(data);const page = document.getPage(0);
try { const text = page.getText(); return text;} finally { page.dispose(); document.dispose(); pdfium.dispose();}Disposal Order
Section titled “Disposal Order”Resources must be disposed in reverse order of creation:
// Creation order: pdfium → document → pageconst pdfium = await PDFium.init();const document = await pdfium.openDocument(data);const page = document.getPage(0);
// Disposal order: page → document → pdfiumpage.dispose();document.dispose();pdfium.dispose();Disposable Classes
Section titled “Disposable Classes”The following classes implement Disposable:
| Class | Synchronous | Notes |
|---|---|---|
PDFium | ✓ | Main library instance |
PDFiumDocument | ✓ | Loaded PDF document |
PDFiumPage | ✓ | Single page |
PDFiumDocumentBuilder | ✓ | Document creation builder |
PDFiumPageBuilder | ✓ | Page content builder |
ProgressivePDFLoader | ✓ | Progressive loading |
WorkerProxy | Async | Uses await using |
Async Disposal
Section titled “Async Disposal”WorkerProxy requires async disposal:
// With await using (recommended)await using proxy = await WorkerProxy.create(workerUrl, wasmBinary);// Disposed asynchronously when scope exits
// Manual async disposalconst proxy = await WorkerProxy.create(workerUrl, wasmBinary);try { // use proxy...} finally { await proxy.dispose();}Loop Patterns
Section titled “Loop Patterns”When processing multiple pages, dispose each before moving to the next:
Generator Pattern
Section titled “Generator Pattern”for (const page of document.pages()) { using p = page; console.log(p.getText());} // Each page disposed after its iterationIndex Pattern
Section titled “Index Pattern”for (let i = 0; i < document.pageCount; i++) { using page = document.getPage(i); console.log(page.getText());} // Page disposed after each iterationCollecting Results
Section titled “Collecting Results”const results: string[] = [];
for (const page of document.pages()) { using p = page; results.push(p.getText());} // Pages disposed, but results array persists
return results;Nested Scopes
Section titled “Nested Scopes”Use block scopes to control disposal timing:
using pdfium = await PDFium.init();using document = await pdfium.openDocument(data);
// First page processing{ using page = document.getPage(0); const result1 = page.render({ scale: 2 }); await saveImage(result1, 'page1.png');} // page1 disposed here
// Second page processing{ using page = document.getPage(1); const result2 = page.render({ scale: 2 }); await saveImage(result2, 'page2.png');} // page2 disposed hereCommon Patterns
Section titled “Common Patterns”Process and Return
Section titled “Process and Return”async function extractFirstPageText(pdfData: Uint8Array): Promise<string> { using pdfium = await PDFium.init(); using document = await pdfium.openDocument(pdfData); using page = document.getPage(0);
return page.getText(); // Resources disposed after return}Conditional Processing
Section titled “Conditional Processing”async function renderIfSmall(data: Uint8Array): Promise<Uint8Array | null> { using pdfium = await PDFium.init(); using document = await pdfium.openDocument(data);
if (document.pageCount > 100) { return null; // Resources still disposed }
using page = document.getPage(0); return page.render({ scale: 2 }).data;}Error Handling
Section titled “Error Handling”async function safeExtract(data: Uint8Array): Promise<string | null> { try { using pdfium = await PDFium.init(); using document = await pdfium.openDocument(data); using page = document.getPage(0);
return page.getText(); } catch (error) { console.error('Extraction failed:', error); return null; } // Resources disposed even on error}Builder Pattern
Section titled “Builder Pattern”async function createPDF(): Promise<Uint8Array> { using pdfium = await PDFium.init(); using builder = pdfium.createDocument();
const font = builder.loadStandardFont('Helvetica');
{ using page = builder.addPage(); page.addText('Hello', 72, 720, font, 24); page.finalize(); } // Page builder disposed
return builder.save(); // Document builder disposed after save}Anti-Patterns
Section titled “Anti-Patterns”Forgetting to Dispose
Section titled “Forgetting to Dispose”// BAD: Resources leakasync function leaky(data: Uint8Array) { const pdfium = await PDFium.init(); const document = await pdfium.openDocument(data); const page = document.getPage(0);
return page.getText(); // No disposal!}
// GOOD: Use usingasync function proper(data: Uint8Array) { using pdfium = await PDFium.init(); using document = await pdfium.openDocument(data); using page = document.getPage(0);
return page.getText();}Storing References
Section titled “Storing References”// BAD: Stored reference becomes invalidlet storedPage: PDFiumPage;
{ using document = await pdfium.openDocument(data); storedPage = document.getPage(0); // Don't store!}// storedPage is now invalid (document disposed)
// GOOD: Process within scope{ using document = await pdfium.openDocument(data); using page = document.getPage(0); const text = page.getText(); // Extract data, not reference}Early Returns Without Disposal
Section titled “Early Returns Without Disposal”// BAD: Early return leaks resourcesfunction processPage(document: PDFiumDocument, index: number) { const page = document.getPage(index);
if (page.width === 0) { return null; // page not disposed! }
const text = page.getText(); page.dispose(); return text;}
// GOOD: using handles all pathsfunction processPage(document: PDFiumDocument, index: number) { using page = document.getPage(index);
if (page.width === 0) { return null; // page still disposed }
return page.getText();}Checking Disposal Status
Section titled “Checking Disposal Status”All disposable classes have a disposed property:
const page = document.getPage(0);console.log(page.disposed); // false
page.dispose();console.log(page.disposed); // true
// Using disposed resource throwspage.getText(); // Throws RESOURCE_DISPOSED errorFinalizationRegistry (Backup)
Section titled “FinalizationRegistry (Backup)”The library uses FinalizationRegistry as a safety net:
- If you forget to dispose, the garbage collector will eventually release resources
- This is NOT reliable for memory-constrained environments
- Always use explicit disposal via
usingordispose()
// The library registers cleanup:const registry = new FinalizationRegistry((handle) => { // Release WASM resource});Summary
Section titled “Summary”| Approach | Pros | Cons |
|---|---|---|
using keyword | Automatic, clean syntax, error-safe | Requires ES2024/TypeScript config |
Manual dispose() | Works everywhere | Verbose, easy to forget, error-prone |
| FinalizationRegistry | Automatic backup | Unreliable timing, not guaranteed |
Recommendation: Use the using keyword whenever possible.
See Also
Section titled “See Also”- Architecture — How the library works
- Memory Management — WASM memory details
- Error Handling — Error patterns