Estoy escribiendo un analizador de lenguaje en javascript y opté por el enfoque funcional de construir funciones anidadas para aumentar la escalabilidad.
Mi código actualmente se parece a lo siguiente
const parseDate = (query) => { return query.match(/stuff/) } const parseAuthor = (query) => { return query.match(/stuff/) } const parseTags = (query) => { return query.match(/stuff/) } //... const transform = (match) => { const target = match?.[0]?.trim() // some more post processing that modifies "target" return target; } const parsers = [parseAuthor, parseTags, parseReviewer, parseSearch, parseAfter].map(parser => { return (query) => transform(parser(query)) })
Estoy tratando de reemplazar la función parseDate
en la matriz con otra función que tomará la salida de parseDate
y la convertirá en formato ISO.
parsers[4] = (query) => { return new Date(parsers[4](query)).toISOString(); };
Esto provoca un RangeError: Maximum call stack size exceeded
lo que supongo que proviene del hecho de que javascript está construyendo una función recursiva que toma la salida de la antigua función parseDate y la ejecuta nuevamente a través de la nueva función, y esa salida a la misma función ... y así.
Eso no es lo que quiero. Solo quiero reemplazar la función de analizadores[4] por una nueva.
Intenté duplicar la función pero no tuve suerte y obtuve el mismo error.
parsers[4] = (query) => { return new Date(parsers[4].bind({})(query)).toISOString(); };
¿Cómo hacemos esto exactamente?
Dado que está agregando una función a la lista (que no se evalúa automáticamente mientras se asigna), su referencia al objeto en el cuarto índice dentro de los parsers
apuntará al estado actual de los analizadores al ejecutar, lo que lo convierte en una referencia propia (lo que lleva a un bucle de recurrencia infinito que hace que el tamaño de la pila explote).
Simplemente podría usar parseDate
en sí mismo si tiene una referencia a él o almacenar el objeto actual en parsers[4]
en una variable temporal antes de usarlo:
var temp = parsers[4] parsers[4] = (query) => { return new Date(temp(query)).toISOString(); };
El problema con cualquiera de las formas de la definición que está tratando de proporcionar es que está definiendo una función, parsers[4]
, que se llama a sí misma incondicionalmente. Intente leerlo y finja que es el motor de Javascript: para calcular los parsers[4](query)
, primero necesitará calcular los parsers[4](query)
y así sucesivamente, infinitamente. (El uso de .bind
no soluciona todo eso; sí, está haciendo una nueva referencia al mismo objeto de función, pero para ejecutar esa función, aún necesita hacer referencia a la misma función que está definiendo).
En cuanto a cómo resolverlo, hay bastantes formas en las que puedo pensar. Probablemente el más simple, pero lo suficientemente bueno si solo lo hace una vez, es hacer una copia temporal:
const oldParser = parsers[4]; parsers[4] = (query) => { return new Date(oldParser(query)).toISOString(); };
También podría escribir esto como un "decorador" o transformación de función:
const transformFunction = (func) => (query) => { return new Date(func(query)).toISOString(); }; parsers[4] = transformFunction(parsers[4]);
Tenga en cuenta que lo anterior no conduce a una recursividad infinita, o de hecho a ninguna recursividad, aunque puede parecer similar a la original. Esto se debe a que el original se refería a los parsers[4]
mientras se ejecutaba , mientras que esto solo se refiere a él una vez, al definir la nueva función. Simplemente, debajo de las sábanas, almacenará una referencia a la función anterior (que he etiquetado como func
), como lo hizo el primer enfoque.
Una ventaja de esto es que, si necesitara transformar toda la matriz de la misma manera, podría hacerlo simplemente como parsers = parsers.map(transformFunction)
.