For AI agents: the complete documentation index is available at /tc39-atlas/en/llms.txt, the full documentation bundle is available at /tc39-atlas/en/llms-full.txt, and this page is available as Markdown at /tc39-atlas/en/proposals/proposal-function-once.md.
  • English
  • Function once S1

    Proposal details
    Proposal overview

    This proposal standardizes a Function.prototype.once method, ensuring a function is called at most once and subsequent calls return the first result. It addresses the common need for one-time callbacks in JavaScript.

    Note

    The README below comes from the upstream repository and may contain outdated stage or status metadata. Use the proposal details above as the current source of truth.

    Function.prototype.once for JavaScript

    ECMAScript Stage-1 Proposal. 2022.

    Co-champions: Hemanth HM; J. S. Choi.

    Rationale

    It is often useful to ensure that callbacks execute only once, no matter how many times are those callbacks called. To do this, developers frequently use “once” functions, which wrap around those callbacks and ensure they are called at most once. This proposal would standardize such a once function in the language core.

    Description

    The Function.prototype.once method would create a new function that calls the original function at most once, no matter how much the new function is called. Arguments given in this call are passed to the original function. Any subsequent calls to the created function would return the result of its first call.

    function f (x) { console.log(x); return x * 2; }
    
    const fOnce = f.once();
    fOnce(3); // Prints 3 and returns 6.
    fOnce(3); // Does not print anything. Returns 6.
    fOnce(2); // Does not print anything. Returns 6.

    Real-world examples

    The following code was adapted to use this proposal.

    From execa@6.1.0:

    export function execa (file, args, options) {
      /* … */
      const handlePromise = async () => { /* … */ };
      const handlePromiseOnce = handlePromise.once();
      /* … */
      return mergePromise(spawned, handlePromiseOnce);
    });

    From glob@7.2.1:

    function Glob (pattern, options, cb) {
      /* … */
      if (typeof cb === 'function') {
        cb = cb.once();
        this.on('error', cb);
        this.on('end', function (matches) {
          cb(null, matches);
        })
      } /* … */
    });

    From Meteor@2.6.1:

    // “Are we running Meteor from a git checkout?”
    export const inCheckout = (function () {
      try { /* … */ } catch (e) { console.log(e); }
      return false;
    }).once();

    From cypress@9.5.2:

    cy.on('command:retry', (() => { /* … */ }).once());

    From jitsi-meet 1.0.5913:

    this._hangup = (() => {
      sendAnalytics(createToolbarEvent('hangup'));
      /* … */
    }).once();

    Precedents and web compatibility

    There is a popular NPM library called once that allows monkey patching, which may raise concerns about Function.prototype.once’s web compatibility.

    However, since its first public version, the once library’s monkey patching has been opt-in only. The monkey patching is not conditional, and there is no actual web-compatibility risk from this library.

    // The default form exports a function.
    once = require('once');
    fOnce = once(f);
    
    // The opt-in form monkey patches Function.prototype.
    require('once').proto();
    fOnce = f.once();

    Other popular once functions from libraries (e.g., lodash.once, Underscore and onetime) also do not use conditional monkey patching.

    A code search for !Function.prototype.once (as in if (!Function.prototype.once) { /* monkey patching */ }) also gave no results in any indexed open-source code. It is unlikely that any production code on the web is conditionally monkey patching a once method into Function.prototype.