preloader

Promisificación con JavaScript

Promisificación con JavaScript

La promisificación es un proceso simple que convierte una función que usa callbacks en una que devuelve una promesa.

Esto es útil en muchos casos prácticos, ya que muchas funciones y bibliotecas están basadas en callbacks, pero las promesas suelen ser más convenientes, por lo que tiene sentido convertirlas.

Veamos un ejemplo:

Aquí tenemos la función loadScript(src, callback) del artículo «Introducción: callbacks»:

				
					function loadScript(src, callback) {
  let script = document.createElement('script');
  script.src = src;

  script.onload = () => callback(null, script);
  script.onerror = () => callback(new Error(`Error al cargar el script ${src}`));

  document.head.append(script);
}

// Uso:
// loadScript('path/script.js', (err, script) => {...})

				
			

La función carga un script con la fuente dada y llama al callback con callback(err) en caso de error o callback(null, script) en caso de éxito. Esto es un uso común de los callbacks que hemos visto antes.

Ahora, vamos a promisificarla.

Crearemos una nueva función loadScriptPromise(src) que hará lo mismo (cargar el script), pero devolverá una promesa en lugar de usar callbacks.

Es decir, pasamos solo src (sin callback) y obtenemos una promesa que se resuelve con el script cuando se carga correctamente y se rechaza con un error en caso contrario.

Aquí está:

				
					let loadScriptPromise = function(src) {
  return new Promise((resolve, reject) => {
    loadScript(src, (err, script) => {
      if (err) reject(err);
      else resolve(script);
    });
  });
};

// Uso:
// loadScriptPromise('path/script.js').then(...)

				
			

Como podemos ver, la nueva función es un contenedor que envuelve la función original loadScript. La llama proporcionando su propio callback y lo traduce a una promesa resolve/reject.

Ahora, loadScriptPromise se adapta bien a un código basado en promesas. Si preferimos las promesas sobre los callbacks (y pronto veremos más razones para ello), la usaremos en su lugar.

En la práctica, puede que necesitemos promisificar más de una función, por lo que tiene sentido usar un ayudante.

Lo llamaremos promisify(f): esta acepta la función a promisificar f y devuelve una función contenedora.

				
					function promisify(f) {
  return function (...args) {
    return new Promise((resolve, reject) => {
      function callback(err, result) {
        if (err) {
          reject(err);
        } else {
          resolve(result);
        }
      }

      args.push(callback);

      f.call(this, ...args);
    });
  };
}

// Uso:
let loadScriptPromise = promisify(loadScript);
loadScriptPromise(...).then(...);

				
			

El código puede parecer complicado, pero esencialmente es lo mismo que escribimos antes al promisificar la función loadScript.

Una llamada a promisify(f) devuelve una función contenedora que envuelve f. Este contenedor devuelve una promesa y redirige la llamada a la función original, siguiendo el resultado en el callback personalizado.

Aquí, promisify asume que la función original espera un callback con dos argumentos (err, result). Eso es lo que usualmente encontramos. Entonces nuestro callback personalizado está exactamente en el formato correcto, y promisify funciona muy bien para tal caso.

¿Y si la función original espera un callback con más argumentos, como callback(err, res1, res2)?

Podemos mejorar el ayudante. Hagamos una versión de promisify más avanzada.

Cuando la llamamos como promisify(f), debe funcionar igual que en la versión previa. Cuando la llamamos como promisify(f, true), debe devolver una promesa que se resuelve con el array de resultados del callback. Esto es para callbacks con muchos argumentos.

				
					// promisify(f, true) para conseguir array de resultados
function promisify(f, manyArgs = false) {
  return function (...args) {
    return new Promise((resolve, reject) => {
      function callback(err, ...results) {
        if (err) {
          reject(err);
        } else {
          resolve(manyArgs ? results : results[0]);
        }
      }

      args.push(callback);

      f.call(this, ...args);
    });
  };
}

// Uso:
f = promisify(f, true);
f(...).then(arrayOfResults => ..., err => ...);

				
			

Como puedes ver, es esencialmente lo mismo de antes, pero resolve es llamado con solo uno o con todos los argumentos dependiendo del valor de manyArgs.

Para formatos más exóticos de callback, como aquellos sin err en absoluto: callback(result), podemos promisificarlos manualmente sin usar el ayudante.

También hay módulos con funciones de promisificación más flexibles, como es6-promisify. En Node.js, hay una función integrada util.promisify para ello.

Nota Importante:

La promisificación es un recurso excelente, especialmente cuando se usa async/await (que cubriremos en el artículo «Async/await»), pero no reemplaza completamente a los callbacks.

Recuerda, una promesa solo puede tener un resultado, pero un callback puede ser llamado varias veces.

Por lo tanto, la promisificación está pensada solo para funciones que llaman al callback una vez. Las llamadas adicionales serán ignoradas.

Related Post

Deja una respuesta

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