{"record":{"id":"7671e199052e09f7","repo":"nodejs/node","slug":"loop-length-for-some-iterators-cannot-be-lazily-ca","errorCode":null,"errorMessage":"Loop length for some iterators cannot be lazily calculated in async mode","messagePattern":"Loop length for some iterators cannot be lazily calculated in async mode","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"tools/inspector_protocol/jinja2/asyncsupport.py","lineNumber":202,"sourceCode":"            yield item\n        return\n    for item in iterable:\n        yield item\n\n\nclass AsyncLoopContext(LoopContextBase):\n\n    def __init__(self, async_iterator, undefined, after, length, recurse=None,\n                 depth0=0):\n        LoopContextBase.__init__(self, undefined, recurse, depth0)\n        self._async_iterator = async_iterator\n        self._after = after\n        self._length = length\n\n    @property\n    def length(self):\n        if self._length is None:\n            raise TypeError('Loop length for some iterators cannot be '\n                            'lazily calculated in async mode')\n        return self._length\n\n    def __aiter__(self):\n        return AsyncLoopContextIterator(self)\n\n\nclass AsyncLoopContextIterator(object):\n    __slots__ = ('context',)\n\n    def __init__(self, context):\n        self.context = context\n\n    def __aiter__(self):\n        return self\n\n    async def __anext__(self):\n        ctx = self.context","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/nodejs/node/blob/1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e/tools/inspector_protocol/jinja2/asyncsupport.py#L184-L220","documentation":"Raised by AsyncLoopContext.length (asyncsupport.py) when loop.length / loop.revindex / loop.last and similar length-derived attributes are requested during async iteration but no upfront length was supplied. Unlike sync mode, an async iterator cannot be cheaply len()-ed, so the length is unknown and these attributes are unavailable.","triggerScenarios":"In an async-enabled template, iterating `{% for x in some_async_gen %}` and inside the loop body referencing `{{ loop.length }}`, `{{ loop.revindex }}`, `{{ loop.last }}`, or `{{ loop.first }}`-style helpers that need the total count.","commonSituations":"Iterating an async generator / async DB cursor in a template and trying to show 'item N of M' progress; reusing a sync template that used loop.length against a now-async data source.","solutions":["Materialize the async iterator into a list before the loop so the length is known: `{% for x in await to_list(source) %}`.","Pre-compute the length and pass it explicitly if the loop construct supports a length argument.","Rewrite the template body to not depend on total count (use loop.index / loop.index0 only, which need no length)."],"exampleFix":"{# before #}\n{% for x in async_source %}{{ loop.length }}{% endfor %}\n{# after #}\n{% set items = async_source | list %}\n{% for x in items %}{{ items | length }}{% endfor %}","handlingStrategy":"validation","validationCode":"async def materialize(ait):\n    out = []\n    async for x in ait:\n        out.append(x)\n    return out\n\n# in template: {% set items = await to_list(source) %} then iterate `items`","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Avoid loop.length/revindex/last over async iterators; use loop.index/index0.","Materialize async iterators into a list before the loop when counts are needed.","Document which template helpers require a known length."],"tags":["async","jinja2","loop","template"],"backgroundTag":null,"analyzedSha":"1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e","analyzedAt":"2026-08-13T00:53:24.642Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}