Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 22 additions & 15 deletions lib/src/compiler/async.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
handleLogEvent,
newCompilePathRequest,
newCompileStringRequest,
promiseWithResolvers,
} from './utils';
import {compilerCommand} from '../compiler-path';
import {activeDeprecationOptions} from '../deprecations';
Expand Down Expand Up @@ -128,23 +129,29 @@ export class AsyncCompiler {
);
dispatcher.logEvents$.subscribe(event => handleLogEvent(options, event));

const compilation = new Promise<proto.OutboundMessage_CompileResponse>(
(resolve, reject) =>
dispatcher.sendCompileRequest(request, (err, response) => {
this.compilations.delete(compilation);
// Reset the compilation ID when the compiler goes idle (no active
// compilations) to avoid overflowing it.
// https://github.com/sass/embedded-host-node/pull/261#discussion_r1429266794
if (this.compilations.size === 0) this.compilationId = 1;
if (err) {
reject(err);
} else {
resolve(response!);
}
}),
);
// Avoid `new Promise()` here because `dispatcher.sendCompilerequest` can
// run its callback synchronously, so `compilation` needs to be assigned
// and in `this.compilations` before we call it.
const {
promise: compilation,
resolve,
reject,
} = promiseWithResolvers<proto.OutboundMessage_CompileResponse>();
this.compilations.add(compilation);

dispatcher.sendCompileRequest(request, (err, response) => {
this.compilations.delete(compilation);
// Reset the compilation ID when the compiler goes idle (no active
// compilations) to avoid overflowing it.
// https://github.com/sass/embedded-host-node/pull/261#discussion_r1429266794
if (this.compilations.size === 0) this.compilationId = 1;
if (err) {
reject(err);
} else {
resolve(response!);
}
});

return handleCompileResponse(await compilation);
} finally {
activeDeprecationOptions.delete(optionsKey);
Expand Down
28 changes: 28 additions & 0 deletions lib/src/compiler/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,3 +228,31 @@ export function handleCompileResponse(
throw utils.compilerError('Compiler sent empty CompileResponse.');
}
}

/**
* A polyfill for the return type of `Promise.withResolvers()` until it's
* universally available in LTS Node.js versions.
*/
interface PromiseWithResolvers<T> {
promise: Promise<T>;
resolve: (value: T | PromiseLike<T>) => void;
// Type definition comes from official types for Node v22.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
reject: (reason?: any) => void;
}

// TODO(nweiz): Replace this with the library function once Node 22 is the
// oldest LTS version (May 2026).
/**
* A polyfill for `Promise.withResolvers()` until it's universally available in
* LTS Node.js versions.
*/
export function promiseWithResolvers<T>(): PromiseWithResolvers<T> {
let resolve: PromiseWithResolvers<T>['resolve'] | undefined;
let reject: PromiseWithResolvers<T>['reject'] | undefined;
const promise = new Promise<T>((resolve_, reject_) => {
resolve = resolve_;
reject = reject_;
});
return {promise, resolve: resolve!, reject: reject!};
}