Las Funciones Flecha en Javascript. Parte 2

23 Jun 2016

Introducción

Las funciones flecha son una de las novedades más interesantes del nuevo estándar ECMAScript 2015. En la primera parte de este artículo hemos podido ver su sintaxis, algunos ejemplos y un ejercicio práctico de refactorización.

Retomamos ahora este tema con más ejemplos, más teoría y algunas limitaciones…

El objeto this

Ya hablamos en su día de uno de los problemas más delicados del lenguaje: el valor de this.

Como repaso rápido que sirva de introducción, este problema (considerado por Douglas Crockford como un bug del lenguaje) implica que el valor referencial de this se asocia a cada uno de los ámbitos/scopes desde el que se invoca, lo cual rompe en parte la teoría general de la Programación Orientada a Objetos:

function Suffixer( suffix ) {
    this.suffix = suffix; // Definition
}
 
Suffixer.prototype.suffixArray = function ( arr ) { // (A)
    'use strict';
 
    return arr.map( function ( x ) { // (B)
        // Doesn’t work:
        return x + this.suffix; // (C)
    } );
};
 
var suff = new Suffixer( '.' + new Date().getTime() );
 
suff.suffixArray( [ 'avatarPic', 'userPic', 'otherImage' ] );

En ese ejemplo anterior, la idea es crear una función que permita añadir sufijos a cada elemento de un array (algo útil por ejemplo para evitar colisiones al guardar nombres de ficheros en base de datos). Sin embargo, el problema lo tenemos en la línea marcada como ‘C’: ahí, this debería estár haciendo referencia al contexto del constructor y, por tanto, acceder al valor asignado en ‘Definition’. Sin embargo, el resultado es un error del intérprete donde se nos avisa de que ‘this is undefined’.

A lo largo de los años, hemos tenido que lidiar con este comportamiento utilizando diversas técnicas: cacheando el valor de this, sobreescribiendo su valor en tiempo de ejecución o utilizando el método bind. Cualquiera de estas formas devenía en un código poco elegante e intuitivo, muy propenso a errores posteriores en posibles refactorizados.

Con las nuevas funciones flecha, esta asociación contextual del valor de this se produce de forma nativa:

Suffixer.prototype.suffixArray = function ( arr ) { // (A)
    'use strict';
 
    return arr.map( x => x + this.suffix );
};

Hemos transfomado la función dentro de map convirtiéndola a la nueva sintaxis. Aprovechamos para repasar: hemos eliminamos la instrucción ‘function‘, suprimido los paréntesis porque solo hay necesitamos un parámetro/argumento, eliminado las llaves del contexto y omitido la palabra reservada ‘return‘.

NOTA: También hemos eliminado el indicador de modo estricto ya que, dentro de este marco contextual, el intérprete simplemente ignora la instrucción.

Si ejecutamos ahora nuestro código, podremos ver que el resultado es el esperado:

suff.suffixArray( [ 'avatarPic', 'userPic', 'otherImage' ] );
// [ "avatarPic.1466594576838", "userPic.1466594576838", "otherImage.1466594576838" ]

Si queremos ir más allá, y a modo de práctica, podemos como en la primera parte de este artículo refactorizar todo el método. Nos quedaría finalmente el siguiente código:

function Suffixer ( suffix ) {
    this.suffix = suffix;
}
 
Suffixer.prototype.suffixArray = arr =>
    arr.map( x => x + this.suffix );

¡¡OJO CON LOS CONSTRUCTORES!!

Si observamos el ejemplo anterior, vemos que el constructor Suffixer podría ser un buen candidato para refactorizar. Si nos metemos con ello, obtendríamos el siguiente código:

var Suffixer = suffix => { this.suffix = suffix; };
 
Suffixer.prototype.suffixArray = arr =>
    arr.map( x => x + this.suffix );

Sin embargo, si probamos a ejecutar nuestro ejercicio, obtendremos un error de sintaxis:

var suff = new Suffixer( '.' + new Date().getTime() );
 
suff.suffixArray( [ 'avatarPic', 'userPic', 'otherImage' ] );
// TypeError: Suffixer.prototype is undefined

Y aquí tenemos una regla importante que no podemos olvidar: las funciones flecha no pueden ser utilizadas como constructores.

Retorno de objetos literales

Otro punto a tener en cuenta es que si pretendemos devolver un objeto literal, la sintaxis no es quizá la más intuitiva:

var getVersion = () => { version: '1.0.0' };
 
console.info( getVersion() ); // undefined

Si arriba, con solo un registro de clave/valor tenemos un resultado inesperado, con más de uno la cosa es peor:

var getSettings = () => {
    baseURL: 'https://openlibra.com',
    appName: 'OpenLibra',
    version: '1.0.0'
};
 
// SyntaxError: missing ; before statement

La cuestión aquí es que, para devolver un objeto de forma literal, la sintaxis de las funciones flecha exige el uso de paréntesis alrededor de las llaves:

var getVersion = () => ( { version: '1.0.0' } );
 
console.info( getVersion() ); // Object { version="1.0.0" }

Para el segundo ejemplo:

var getSettings = () => ( {
    baseURL: 'https://openlibra.com',
    appName: 'OpenLibra',
    version: '1.0.0'
} );
 
console.info( getSettings() );
// Object { baseURL= "https://openlibra.com", appName="OpenLibra", version="1.0.0" }

La explicación de esta necesidad de paréntesis es que, en ausencia de ellas, el bloque de llaves se interpreta como una secuencia de sentencias. Eso quiere decir que para Javascript, el código sería similar al que vimos en su día para la definición de bloques:

function test ( param1, param2 ) {
 
    declareVars: {
        var myConstant = 100,
        result;
    }
 
    processing: {
        result = myConstant + param1 + param2;
    }
 
    printResults: {
        return result;
    }
 
}

En definitiva, los bloques que utilizamos para organizar el código en el cuerpo de una función tradicional, se puede convertir en un posible error de sintaxis en las nuevas estructuras de tipo flecha si no tenemos cuidado.

Indentado y organización

Dado el formato de estas funciones, en ocasiones podemos terminar con líneas de código demasiado largas. Por ejemplo, en un tratamiento de promesas podríamos ver algo similar a esto:

asyncFunction().then( () => responseA() ).then( () => responseB() ).done( () => finish );

Para reforzar la legibilidad, la siguiente tabla muestra dónde el intérprete nos permite introducir saltos de línea:

var func1 = ( x, y ) // SyntaxError
=> {
    return x + y;
};
 
var func2 = ( x, y ) => // OK
{
    return x + y;
};
 
var func3 = ( x, y ) => { // OK
    return x + y;
};
 
var func4 = ( x, y ) // SyntaxError
=> x + y;
 
var func5 = ( x, y ) => // OK
x + y;
 
var func6 = ( // OK
    x,
    y
) => {
    return x + y;
};

Invocación inmediata de funciones flecha

Al igual que ocurre las funciones tradicionales autoejecutablesIIFE-, las funciones flecha también permiten su ejecución inmediata –IIAF– (Immediately-invoked arrow functions):

( () => {
    return 'Hello World';
} ) ();

Esa construcción que quizá recuerda con cierta nostalgia al lenguaje LISP, permite todo el juego de malabares propio que llevamos utilizando desde hace una década. Por ejemplo, podríamos implementar el patrón del módulo extendido jugando con los parámetros/argumentos.

Primero, la versión ‘clásica’:

// file: core.js
var MODULE = ( function () {
    var my = {},
        privateVariable = 1;
 
    function privateMethod () {
        // ...
    }
 
    my.moduleMethod = function () {
        console.info( 'Method from core...' );
    };
 
    return my;
} () );
 
// file: plugin.js
var MODULE = ( function ( my ) {
    my.anotherMethod = function () {
        console.info( 'Method from plugin...' );
    };
 
    return my;
} ( MODULE || {} ) );
 
console.info( MODULE.moduleMethod() ); // Method from core...
console.info( MODULE.anotherMethod() ); // Method from plugin...

Y, ahora, la versión con flechas:

// file: core.js
var MODULE = ( () => {
    var my = {},
        privateVariable = 1;
 
    var privateMethod = () => {
        // ...
    };
 
    my.moduleMethod = () => console.info( 'Method from core...' );
 
    return my;
} ) ();
 
 
// file: plugin.js
var MODULE = ( ( my ) => {
    my.anotherMethod = () => console.info( 'Method from plugin...' );
 
    return my;
} ) ( MODULE || {} );
 
console.info( MODULE.moduleMethod() ); // Method from core...
console.info( MODULE.anotherMethod() ); // Method from plugin...

NOTA: De nuevo, animo a que probéis a realizar este tipo de refactorizados para ir practicando con la sintaxis de estas nuevas funciones.

Patrones más complejos: aplicaciones parciales y el currying

Ya expliqué en su día el concepto del currying en Javascript, incluso lo volvimos a tocar de pasada cuando nos referimos a las Funciones Puras al hablar sobre el beneficio de éstas con la Memoization.

La idea principal proviene de la Programación Funcional y se define como el proceso de fijar valores a los argumentos de una función antes de que sea invocada.

Retomemos nuestro ejemplo más simple de función pura -que también hemos utilizado aquí para hablar de la sintaxis de las funciones flecha-:

function add ( x, y ) {
    return x + y;
}

O su versión en la nueva sintaxis:

var add = ( x, y ) => x + y;

Si necesitamos crear una aplicación parcial a partir del fragmento anterior para, por ejemplo, crear una nueva función que sume 1 a un número dado, podríamos escribir lo siguiente:

var plus1 = y => add( 1, y );
 
console.info( plus1( 10 ) ); // 11
console.info( plus1( 7 ) ); // 8

Como se puede deducir del código anterior, las posibilidades son muy interesantes cuando buscamos la reutilización del código y la diversificación de cómputos.

¿Y el currying?

Pues podríamos re plantearlo usando el clásico ejemplo de la función incrementadora. Si tenemos:

function writeSeq ( start, end ) {
    for( var i = start; i <= end; ++i ) {
        console.log( i );
    }
}

O mejor su versión en modo flecha:

var writeSeq = ( start, end ) => {
    for( var i = start; i <= end; ++i ) {
        console.log( i );
    }
}

Podemos implementar un currying de forma simple con la mínima expresión de esta funcionalidad:

var curry = ( fn, ...args1 ) => ( ...args2 ) => fn( ...args1, ...args2 );

O si queremos indentarla para que no sea tan larga:

var curry = ( fn, ...args1 ) =>
    ( ...args2 ) =>
        fn( ...args1, ...args2 );

Esa ‘simple’ línea de código nos permite ahora fijar el valor para funciones derivadas:

var seq20 = curry( writeSeq, 20 );
seq20( 25 ); // 20, 21, 22, 23, 24

Realmente simple; el poder combinado de esta sintaxis junto con el del parámetro de arrastre, permite reducir mucho el código fuente…

Conclusión (parte 2)

Y esto es todo para esta segunda parte del artículo; hemos podido comprobar algunos usos más complejos -y limitaciones- de esta nueva forma de entender las funciones en Javascript, hemos tratado el valor de this, y hemos refactorizado más código para acostumbrarnos a la nueva sintaxis.

Queda pendiente una tercera -y última- parte donde se verán las correspondencias entre las Funciones Flechas y el tradicional método bind, y donde comentaremos la compatibilidad/cobertura actual de esta estructura.

Más:

{4} Comentarios.

  1. Julio Jaramillo

    Error de escritura: hemos eliminamos la instrucción ‘function‘.

    Saludos

  2. David

    No he entendido bien el tema del this. Por ejemplo:

    var x = () => {
    	console.log(this);
    	(function(){
    		console.log(this);	
    	})();
    }
    var o = {x:x}
    o.x();
    

    El resultado en la consola es la página en la que se ejecuta el código y no el objeto “o” como sería de esperar en el primer “log”. ¿Qué aporta en esta situación?

    • Carlos Benítez

      Efectivamente, en ambos casos, this apunta al objeto general (la página)… pero precisamente es lo que cabría esperar.

      Con tu ‘o’, has creado un objeto literal (no una instancia) que hace referencia a la función ‘x’ (global).

  3. David

    > … pero precisamente es lo que cabría esperar.

    No había caído en eso. La verdad es que sí, es mucho más coherente este sistema.

    Gracias

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *