Función en JavaScript que solo se puede llamar una vez

116

Necesito crear una función que se pueda ejecutar solo una vez, cada vez después de la primera no se ejecutará. Sé de C ++ y Java acerca de las variables estáticas que pueden hacer el trabajo, pero me gustaría saber si hay una forma más elegante de hacer esto.

vlio20
fuente
~ 8 años después, creé una solicitud de función a mis decoradores lib ( github.com/vlio20/utils-decorators ) para tener un decorador que haría exactamente eso ( github.com/vlio20/utils-decorators/issues/77 )
vlio20

Respuestas:

221

Si por "no se ejecutará" quiere decir "no hará nada cuando se le llame más de una vez", puede crear un cierre:

var something = (function() {
    var executed = false;
    return function() {
        if (!executed) {
            executed = true;
            // do something
        }
    };
})();

something(); // "do something" happens
something(); // nothing happens

En respuesta a un comentario de @Vladloffe (ahora eliminado): Con una variable global, otro código podría restablecer el valor de la bandera "ejecutado" (cualquier nombre que elija). Con un cierre, otro código no tiene forma de hacer eso, ya sea accidental o deliberadamente.

Como señalan otras respuestas aquí, varias bibliotecas (como Underscore y Ramda ) tienen una pequeña función de utilidad (normalmente llamada once()[*] ) que acepta una función como argumento y devuelve otra función que llama a la función proporcionada exactamente una vez, independientemente de cómo muchas veces se llama a la función devuelta. La función devuelta también almacena en caché el valor devuelto primero por la función proporcionada y lo devuelve en llamadas posteriores.

Sin embargo, si no está utilizando una biblioteca de terceros, pero aún desea una función de utilidad (en lugar de la solución nonce que ofrecí anteriormente), es bastante fácil de implementar. La versión más bonita que he visto es esta publicada por David Walsh :

function once(fn, context) { 
    var result;
    return function() { 
        if (fn) {
            result = fn.apply(context || this, arguments);
            fn = null;
        }
        return result;
    };
}

Me inclinaría a cambiar fn = null;a fn = context = null;. No hay ninguna razón para que el cierre mantenga una referencia a contextuna vez que fnse ha llamado.

[*] Tenga en cuenta, sin embargo, que otras bibliotecas, como esta extensión de Drupal para jQuery , pueden tener una función nombrada once()que hace algo bastante diferente.

Ted Hopp
fuente
1
funciona muy bien, ¿puede explicar el inicio de sesión detrás de él, cómo var ejecutado = falso; trabaja
Amr.Ayoub
@EgyCode: esto se explica muy bien en la documentación de MDN sobre cierres .
Ted Hopp
lo siento, me refería a la lógica, nunca entendí la var booleana y cómo funciona en ese caso ejecutado
Amr.Ayoub
1
@EgyCode: en ciertos contextos (como en una ifexpresión de prueba de declaración), JavaScript espera uno de los valores trueo falsey el flujo del programa reacciona de acuerdo con el valor encontrado cuando se evalúa la expresión. Los operadores condicionales como ==siempre evalúan a un valor booleano. Una variable también puede contener trueo false. (Para obtener más información, consulte la documentación sobre booleano , veraz y falsey .)
Ted Hopp
No entiendo por qué la ejecución no se restablece con cada nueva llamada. ¿Alguien puede explicarlo, por favor?
Juanse Cora
64

Reemplácelo con una función NOOP (sin operación) reutilizable .

// this function does nothing
function noop() {};

function foo() {
    foo = noop; // swap the functions

    // do your thing
}

function bar() {
    bar = noop; // swap the functions

    // do your thing
}
Odio la pereza
fuente
11
@fableal: ¿Cómo es esto poco elegante? Nuevamente, es muy limpio, requiere menos código y no requiere una nueva variable para cada función que deba deshabilitarse. Un "noop" está diseñado exactamente para este tipo de situación.
I Hate Lazy
1
@fableal: Acabo de ver la respuesta de hakra. Entonces, ¿crear un nuevo cierre y variable cada vez que necesite hacer esto en una nueva función? Tienes una definición muy divertida de "elegante".
I Hate Lazy
2
De acuerdo con la respuesta de asawyer, solo necesitaba hacer _.once (foo) o _.once (bar), y las funciones en sí mismas no necesitan ser conscientes de que se ejecutan solo una vez (sin necesidad de noop y sin necesidad de el * = noop).
fábula
7
Realmente no es la mejor solución. Si está pasando esta función como una devolución de llamada, aún se puede llamar varias veces. Por ejemplo: setInterval(foo, 1000)- y esto ya no funciona. Simplemente está sobrescribiendo la referencia en el alcance actual.
un gato
1
invalidateFunción reutilizable que funciona con setIntervaletc: jsbin.com/vicipar/1/edit?js,console
Q20
32

Apunta a una función vacía una vez que se ha llamado:

function myFunc(){
     myFunc = function(){}; // kill it as soon as it was called
     console.log('call once and never again!'); // your stuff here
};
<button onClick=myFunc()>Call myFunc()</button>


O, así:

var myFunc = function func(){
     if( myFunc.fired ) return;
     myFunc.fired = true;
     console.log('called once and never again!'); // your stuff here
};

// even if referenced & "renamed"
((refToMyfunc)=>{
  setInterval(refToMyfunc, 1000);
})(myFunc)

vsync
fuente
1
Esta solución está mucho más en el espíritu de un lenguaje altamente dinámico como Javascript. ¿Por qué establecer semáforos cuando simplemente puede vaciar la función una vez que se ha utilizado?
Ivan Čurdinjaković
¡Muy buena solución! Esta solución también está funcionando mejor que el enfoque de cierre. El único "inconveniente" menor es que debe mantener sincronizado el nombre de la función si cambia el nombre.
Lionel
5
El problema con esto es que si hay otra referencia a la función en algún lugar (por ejemplo, se pasó como un argumento y se escondió en otra variable en algún lugar, como en una llamada a setInterval()), la referencia repetirá la funcionalidad original cuando se llame.
Ted Hopp
@TedHopp - aquí hay un tratamiento especial para esos casos
vsync
1
Sí, esa es exactamente la respuesta de Bunyk en este hilo. También es similar a un cierre (como en mi respuesta ) pero usa una propiedad en lugar de una variable de cierre. Ambos casos son bastante diferentes de su enfoque en esta respuesta.
Ted Hopp
25

UnderscoreJs tiene una función que hace eso, underscorejs.org/#once

  // Returns a function that will be executed at most one time, no matter how
  // often you call it. Useful for lazy initialization.
  _.once = function(func) {
    var ran = false, memo;
    return function() {
      if (ran) return memo;
      ran = true;
      memo = func.apply(this, arguments);
      func = null;
      return memo;
    };
  };
asawyer
fuente
1
Me parece gracioso onceaceptar argumentos. Puede hacer squareo = _.once(square); console.log(squareo(1)); console.log(squareo(2));y obtener 1ambas llamadas a squareo. ¿Estoy entendiendo esto bien?
Aschmied
@aschmied Tienes razón: el resultado del conjunto de argumentos de la primera llamada se memomizará y se devolverá para todas las demás llamadas independientemente del parámetro, ya que la función subyacente nunca se vuelve a llamar. En casos como ese, no sugiero utilizar el _.oncemétodo. Ver jsfiddle.net/631tgc5f/1
asawyer
1
@aschmied O supongo que use una llamada separada a una vez por conjunto de argumentos. No creo que esto esté realmente destinado a ese tipo de uso.
asawyer
1
Útil si ya lo está usando _; No recomendaría depender de toda la biblioteca para un código tan pequeño.
Joe Shanahan
11

Hablando de variables estáticas, esto es un poco como una variante de cierre:

var once = function() {
    if(once.done) return;
    console.log('Doing this once!');
    once.done = true;
};

once(); once(); 

A continuación, puede restablecer una función si lo desea:

once.done = false;
Bunyk
fuente
4
var quit = false;

function something() {
    if(quit) {
       return;
    } 
    quit = true;
    ... other code....
}
Diodeus - James MacFarlane
fuente
Vea la respuesta de Ted Hopp. Es una forma de definir el alcance de las variables a nivel de función.
Diodeus - James MacFarlane
3

Simplemente puede hacer que la función "se elimine"

function Once(){
    console.log("run");

    Once = undefined;
}

Once();  // run
Once();  // Uncaught TypeError: undefined is not a function 

Pero esta puede no ser la mejor respuesta si no quiere tragar errores.

También puedes hacer esto:

function Once(){
    console.log("run");

    Once = function(){};
}

Once(); // run
Once(); // nothing happens

Necesito que funcione como un puntero inteligente, si no hay elementos del tipo A, se puede ejecutar, si hay uno o más elementos A, la función no se puede ejecutar.

function Conditional(){
    if (!<no elements from type A>) return;

    // do stuff
}
Shmiddty
fuente
1
Necesito que funcione como un puntero inteligente, si no hay elementos del tipo A, se puede ejecutar, si hay uno o más elementos A, la función no se puede ejecutar.
vlio20
@VladIoffe Eso no es lo que preguntaste.
Shmiddty
Esto no funcionará si Oncese pasa como una devolución de llamada (por ejemplo, setInterval(Once, 100)). Se seguirá llamando a la función original.
Ted Hopp
2

prueba esto

var fun = (function() {
  var called = false;
  return function() {
    if (!called) {
      console.log("I  called");
      called = true;
    }
  }
})()
Database_Query
fuente
2

De un tipo llamado Crockford ... :)

function once(func) {
    return function () {
        var f = func;
        func = null;
        return f.apply(
            this,
            arguments
        );
    };
}
Jowen
fuente
1
Esto es genial si crees que TypeError: Cannot read property 'apply' of nulles genial. Eso es lo que obtiene la segunda vez que invoca la función devuelta.
Ted Hopp
2

invalidateFunción reutilizable que funciona con setInterval:

var myFunc = function (){
  if (invalidate(arguments)) return;
  console.log('called once and never again!'); // your stuff here
};

const invalidate = function(a) {
  var fired = a.callee.fired;
  a.callee.fired = true;
  return fired;
}

setInterval(myFunc, 1000);

Pruébelo en JSBin: https://jsbin.com/vicipar/edit?js,console

Variación de respuesta de Bunyk

Q20
fuente
1

Aquí hay un ejemplo de JSFiddle: http://jsfiddle.net/6yL6t/

Y el codigo:

function hashCode(str) {
    var hash = 0, i, chr, len;
    if (str.length == 0) return hash;
    for (i = 0, len = str.length; i < len; i++) {
        chr   = str.charCodeAt(i);
        hash  = ((hash << 5) - hash) + chr;
        hash |= 0; // Convert to 32bit integer
    }
    return hash;
}

var onceHashes = {};

function once(func) {
    var unique = hashCode(func.toString().match(/function[^{]+\{([\s\S]*)\}$/)[1]);

    if (!onceHashes[unique]) {
        onceHashes[unique] = true;
        func();
    }
}

Podrías hacerlo:

for (var i=0; i<10; i++) {
    once(function() {
        alert(i);
    });
}

Y solo se ejecutará una vez :)

Aldekein
fuente
1

Configuración inicial:

var once = function( once_fn ) {
    var ret, is_called;
    // return new function which is our control function 
    // to make sure once_fn is only called once:
    return function(arg1, arg2, arg3) {
        if ( is_called ) return ret;
        is_called = true;
        // return the result from once_fn and store to so we can return it multiply times:
        // you might wanna look at Function.prototype.apply:
        ret = once_fn(arg1, arg2, arg3);
        return ret;
    };
}
andlrc
fuente
1

Si usa Node.js o escribe JavaScript con browserify, considere el módulo npm "once" :

var once = require('once')

function load (file, cb) {
  cb = once(cb)
  loader.load('file')
  loader.once('load', cb)
  loader.once('error', cb)
}
orcaman
fuente
1

Si desea poder reutilizar la función en el futuro, esto funciona bien según el código de ed Hopp anterior (¡me doy cuenta de que la pregunta original no requería esta función adicional!):

   var something = (function() {
   var executed = false;              
    return function(value) {
        // if an argument is not present then
        if(arguments.length == 0) {               
            if (!executed) {
            executed = true;
            //Do stuff here only once unless reset
            console.log("Hello World!");
            }
            else return;

        } else {
            // otherwise allow the function to fire again
            executed = value;
            return;
        }       
    }
})();

something();//Hello World!
something();
something();
console.log("Reset"); //Reset
something(false);
something();//Hello World!
something();
something();

La salida se ve así:

Hello World!
Reset
Hello World!
CMP
fuente
1

decorador simple que es fácil de escribir cuando lo necesita

function one(func) {
  return function () {
     func && func.apply(this, arguments);
     func = null;
  }
}

utilizando:

var initializer= one( _ =>{
      console.log('initializing')
  })

initializer() // 'initializing'
initializer() // nop
initializer() // nop
pery mimon
fuente
0

Intentando utilizar la función de subrayado "una vez":

var initialize = _.once(createApplication);
initialize();
initialize();
// Application is only created once.

http://underscorejs.org/#once

Andrew Feng
fuente
nah, es demasiado feo cuando empiezas a llamarlo con argumentos.
vsync
0
var init = function() {
    console.log("logges only once");
    init = false;
}; 

if(init) { init(); }

/* next time executing init() will cause error because now init is 
   -equal to false, thus typing init will return false; */
RegarBoy
fuente
0

Si está utilizando Ramda, puede utilizar la función "una vez" .

Una cita de la documentación:

once Función (a… → b) → (a… → b) PARÁMETROS Agregados en v0.1.0

Acepta una función fn y devuelve una función que protege la invocación de fn de manera que solo se puede llamar a fn una vez, sin importar cuántas veces se invoque la función devuelta. El primer valor calculado se devuelve en invocaciones posteriores.

var addOneOnce = R.once(x => x + 1);
addOneOnce(10); //=> 11
addOneOnce(addOneOnce(50)); //=> 11
David
fuente
0

Mantenlo lo más simple posible

function sree(){
  console.log('hey');
  window.sree = _=>{};
}

Puedes ver el resultado

resultado del script

Shreeketh K
fuente
Si está dentro del módulo, solo use en thislugar dewindow
Shreeketh K
0

JQuery permite llamar a la función solo una vez usando el método uno () :

let func = function() {
  console.log('Calling just once!');
}
  
let elem = $('#example');
  
elem.one('click', func);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
  <p>Function that can be called only once</p>
  <button id="example" >JQuery one()</button>
</div>

Implementación usando el método JQuery en () :

let func = function(e) {
  console.log('Calling just once!');
  $(e.target).off(e.type, func)
}
  
let elem = $('#example');
  
elem.on('click', func);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
  <p>Function that can be called only once</p>
  <button id="example" >JQuery on()</button>
</div>

Implementación usando JS nativo:

let func = function(e) {
  console.log('Calling just once!');
  e.target.removeEventListener(e.type, func);
}
  
let elem = document.getElementById('example');
  
elem.addEventListener('click', func);
<div>
  <p>Functions that can be called only once</p>
  <button id="example" >ECMAScript addEventListener</button>
</div>

Nikita
fuente
-1
if (!window.doesThisOnce){
  function myFunction() {
    // do something
    window.doesThisOnce = true;
  };
};
atw
fuente
Es una mala práctica contaminar el alcance global (también conocido como ventana)
vlio20
Estoy de acuerdo contigo, pero alguien podría sacar algo de eso.
atw
Esto no funciona. Cuando ese código se ejecuta por primera vez, se crea la función. Luego, cuando se llama a la función, se ejecuta y el valor global se establece en falso, pero la función aún se puede llamar la próxima vez.
trincot
No se establece como falso en ninguna parte.
atw
-2

Este es útil para prevenir bucles infinitos (usando jQuery):

<script>
var doIt = true;
if(doIt){
  // do stuff
  $('body').html(String($('body').html()).replace("var doIt = true;", 
                                                  "var doIt = false;"));
} 
</script>

Si le preocupa la contaminación del espacio de nombres, sustituya "doIt" por una cadena larga y aleatoria.

Elliot Gorokhovsky
fuente
-2

Ayuda a prevenir la ejecución pegajosa

var done = false;

function doItOnce(func){
  if(!done){
    done = true;
    func()
  }
  setTimeout(function(){
    done = false;
  },1000)
}
Gleb Dolzikov
fuente