Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async filters don't work if render callback is not used #1474

Open
olegsmetanin opened this issue Sep 5, 2024 · 4 comments
Open

Async filters don't work if render callback is not used #1474

olegsmetanin opened this issue Sep 5, 2024 · 4 comments

Comments

@olegsmetanin
Copy link

olegsmetanin commented Sep 5, 2024

"nunjucks": "^3.2.4",

Async filter

.addFilter('await', async function (value, cb) {
  const res = await new Promise((_resolve, _reject) => {
    _resolve(value)
  })
  cb(null, res)
}, true)

with template

{% asyncEach rec in ["qwe"] | await %}
{{ rec }}
{% endeach %}

doesn't work and renders nothing

@geleto
Copy link

geleto commented Sep 13, 2024

This is how I do it:

.addFilter('await', function (promise: Promise<any>, callback: (err: Error | null, result?: any) => void) {
	promise.then(result => {
		callback(null, result);
	}).catch(err => {
		callback(err);
	});
}, true);

@olegsmetanin
Copy link
Author

olegsmetanin commented Sep 13, 2024

@geleto it doesn't work for any real async case, just try to send setTimeout promise with 0 delay to this filter. I expect the following filter will work and render "result":

.addFilter('await', function (_in: any, callback: (err: Error | null, result?: any) => void) {
	const promise = new Promise((resolve) => {
          setTimeout(() => {
            resolve('result')
          }, 0)
        });
        promise.then(result => {
		callback(null, result);
	}).catch(err => {
		callback(err);
	});
}, true);

@geleto
Copy link

geleto commented Sep 13, 2024

Here are some example tests for real async cases:

import { expect } from 'chai';
import * as nunjucks from 'nunjucks';

describe('await filter tests', () => {

	let env: nunjucks.Environment;

	beforeEach(() => {
		env = new nunjucks.Environment();
		// Implement 'await' filter for resolving promises in templates
		env.addFilter('await', function (promise: Promise<any>, callback: (err: Error | null, result?: any) => void) {
			promise.then(result => {
				callback(null, result);
			}).catch(err => {
				callback(err);
			});
		}, true);
	});

	it('should handle custom async filter with global async function', (done) => {
		// Add global async function
		env.addGlobal('fetchUser', async (id: number) => {
			// Simulate async operation
			await new Promise(resolve => setTimeout(resolve, 10));
			return { id, name: `User ${id}` };
		});

		const template = '{% set user = fetchUser(123) | await %}Hello, {{ user.name }}!';

		env.renderString(template, {}, (err, result) => {
			if (err) return done(err);
			expect(result).to.equal('Hello, User 123!');
			done();
		});
	});

	it('should handle asyncEach with array of promises', (done) => {
		// Add global function to fetch records
		env.addGlobal('getRecords', () => {
			// Return an array of promises (each record is a promise)
			return [
				new Promise(resolve => setTimeout(() => resolve('Record 1'), 10)),
				new Promise(resolve => setTimeout(() => resolve('Record 2'), 20)),
				new Promise(resolve => setTimeout(() => resolve('Record 3'), 15))
			];
		});

		const template = `{%- set records = getRecords() -%}
		{%- asyncEach rec in records -%}
		{{ rec | await }}{% if not loop.last %}\n{% endif %}
		{%- endeach %}`;;

		env.renderString(template, {}, (err, result) => {
			if (err) return done(err);
			expect((result as string).trim()).to.equal('Record 1\nRecord 2\nRecord 3');
			done();
		});
	});
});

@olegsmetanin
Copy link
Author

olegsmetanin commented Sep 13, 2024

@geleto Thank you! It seems that we can't call renderString without callback if we have async filters

import { expect } from 'chai';
import * as nunjucks from 'nunjucks';

describe('await filter tests', () => {

	let env: nunjucks.Environment;

	beforeEach(() => {
		env = new nunjucks.Environment();

		env.addFilter('fetchUser', async (id: string, callback: (err: Error | null, result?: any) => void) => {
			// Simulate async operation
			const promise = new Promise(resolve => setTimeout(() => resolve(`User ${id}`), 10));
			promise.then(result => {
				callback(null, result);
			}).catch(err => {
				callback(err);
			});
		}, true);
	});

	it('should render using renderString without callback', () => {
		const template = '{% set user = "User 123" %}Hello, {{ user }}!';
		const result = env.renderString(template, {});
		expect(result).to.equal('Hello, User 123!');
	});

	it('should handle custom async filter using renderString with callback', (done) => {
		const template = '{% set user = "123" | fetchUser %}Hello, {{ user }}!';
		env.renderString(template, {}, (err, result) => {
			if (err) return done(err);
			expect(result).to.equal('Hello, User 123!');
			done();
		});
	});

	// Failed!
	it('should handle custom async filter using renderString without callback', () => {
		const template = '{% set user = "123" | fetchUser %}Hello, {{ user }}!';
		const result = env.renderString(template, {});
		expect(result).to.equal('Hello, User 123!');
	});

});


  await filter tests
     should render using renderString without callback
     should handle custom async filter using renderString with callback
    1) should handle custom async filter using renderString without callback


  2 passing (24ms)
  1 failing

  1) await filter tests
       should handle custom async filter using renderString without callback:
     AssertionError: expected null to equal 'Hello, User 123!'

@olegsmetanin olegsmetanin changed the title Async filters doesn't work Async filters don't work if render callback is not used Sep 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants