Eric Elliott

Seguir

Jan 23, 2017 · 11 min leer

Foto por Kabun (CC BY NC SA 2.,0)

«Master the JavaScript Interview» es una serie de publicaciones diseñadas para preparar a los candidatos para preguntas comunes que es probable que encuentren al solicitar un puesto de JavaScript de nivel medio a alto. Estas son preguntas que uso con frecuencia en entrevistas reales.

una promesa es un objeto que puede producir un solo valor en algún momento en el futuro: ya sea un valor resuelto, o una razón por la que no se ha resuelto (por ejemplo, se ha producido un error de red)., Una promesa puede estar en uno de los 3 estados posibles: cumplida, rechazada o pendiente. Promise los usuarios pueden adjuntar devoluciones de llamada para manejar el valor cumplido o la razón del rechazo.

Las promesas están ansiosas, lo que significa que una promesa comenzará a hacer cualquier tarea que le des tan pronto como se invoque el constructor de promesas. Si necesita pereza, consulte observables o tareas.

An Incomplete History of Promises

las primeras implementaciones de promesas y futuros (una idea similar / relacionada) comenzaron a aparecer en idiomas como MultiLisp y Concurrent Prolog ya en la década de 1980., El uso de la palabra «promesa» fue acuñado por Barbara Liskov y Liuba Shrira en 1988.

la primera vez que escuché sobre promesas en JavaScript, Node era completamente nuevo y la comunidad estaba discutiendo la mejor manera de manejar el comportamiento asíncrono. La comunidad experimentó con promesas por un tiempo, pero finalmente se conformó con las devoluciones de llamada Node-standard error-first.

casi al mismo tiempo, Dojo agregó promesas a través de la API diferida. El creciente interés y la actividad finalmente llevaron a la recién formada Especificación Promises/a diseñada para hacer varias promesas más interoperables.,

los comportamientos asincrónicos de jQuery fueron refactorizados alrededor de promesas. el soporte promise de jQuery tenía notables similitudes con el Deferred de Dojo, y rápidamente se convirtió en la implementación promise más utilizada en JavaScript debido a la inmensa popularidad de jQuery, por un tiempo. Sin embargo, no soportaba el comportamiento de encadenamiento de dos canales (cumplido/rechazado) & gestión de excepciones con la que la gente contaba para construir herramientas sobre promesas.,

a pesar de esas debilidades, jQuery oficialmente hizo que JavaScript promises se generalizara, y las mejores bibliotecas de promesas independientes como Q, When y Bluebird se hicieron muy populares. las incompatibilidades de implementación de jQuery motivaron algunas aclaraciones importantes en la especificación promise, que fue reescrita y rebautizada como la especificación Promises/a+.,

ES6 trajo un Promises/a+ compatible Promise global, y algunas API muy importantes se construyeron sobre el nuevo soporte estándar de Promise: notablemente la especificación WHATWG Fetch y el estándar de funciones Async (un borrador de Etapa 3 en el momento de escribir esto).

las promesas descritas aquí son las que son compatibles con la especificación Promises/a+, con un enfoque en la implementación del estándar ECMAScript Promise.

cómo funcionan las promesas

una promesa es un objeto que se puede devolver sincrónicamente desde una función asíncrona., Estará en uno de los 3 estados posibles:

una promesa se resuelve si no está pendiente (se ha resuelto o rechazado). A veces la gente usa resuelto y resuelto para significar lo mismo: No pendiente.

una vez establecida, una promesa no puede ser reasentada. Llamar resolve() o reject() nuevo no tendrá ningún efecto. La inmutabilidad de una promesa establecida es una característica importante.

Native JavaScript promises don’t expose promise states. En cambio, se espera que trates la promesa como una caja negra., Solo la función responsable de crear la promesa tendrá conocimiento del Estado de la promesa, o acceso para resolver o rechazar.

Aquí hay una función que devuelve una promesa que se resolverá después de un retraso de tiempo especificado:

wait — promise example on CodePen

Our wait(3000)la llamada esperará 3000ms (3 segundos), y luego registrará 'Hello!'., Todas las promesas compatibles con especificaciones definen un método .then() que se utiliza para pasar controladores que pueden tomar el valor resuelto o rechazado.

El constructor de promesas ES6 toma una función. Que la función toma dos parámetros, resolve() y reject(). En el ejemplo anterior, solo usamos resolve(), así que dejé reject() fuera de la lista de parámetros. Luego llamamos a setTimeout() para crear el retraso, y llamamos a resolve() cuando haya terminado.,

puede opcionalmente resolve()o reject()con valores, que se pasarán a las funciones de devolución de llamada adjuntas con .then().

I reject() con un valor, siempre me pasa un Error objeto. Generalmente quiero dos estados de resolución posibles: el camino feliz normal, o una excepción — cualquier cosa que detenga el camino feliz normal de suceder. Pasar un objeto Error lo hace explícito.,

reglas de promesa importantes

Un estándar para promesas fue definido por la Comunidad de especificaciones Promises / a+. Hay muchas implementaciones que se ajustan al estándar, incluyendo las promesas ECMAScript estándar de JavaScript.

Las promesas que siguen la especificación deben seguir un conjunto específico de reglas:

  • Una promesa o «thenable» es un objeto que proporciona un método.then() compatible con el estándar.
  • Una promesa pendiente puede pasar a un estado cumplido o rechazado.,
  • Una promesa cumplida o rechazada se resuelve, y no debe transitar a ningún otro estado.
  • Una vez que se liquida una promesa, debe tener un valor (que puede ser undefined). Ese valor no debe cambiar.

El Cambio en este contexto se refiere a la comparación de identidad (===). Un objeto puede ser usado como el valor cumplido, y las propiedades del objeto pueden mutar.,

Cada promesa debe suministrar un .then() método con la siguiente firma:

promise.then(
onFulfilled?: Function,
onRejected?: Function
) => Promise

El .then() método debe cumplir con las siguientes reglas:

  • Tanto onFulfilled() y onRejected() son opcionales.
  • si los argumentos suministrados no son funciones, deben ignorarse.
  • onFulfilled() se llamará después de que se cumpla la promesa, con el valor de la promesa como primer argumento.,
  • onRejected() se llamará después de que se rechace la promesa, con el motivo del rechazo como primer argumento. La razón puede ser cualquier valor válido de JavaScript, pero debido a que los rechazos son esencialmente sinónimos de excepciones, recomiendo usar objetos de Error.
  • Ni onFulfilled() ni onRejected() puede ser llamado más de una vez.
  • .then() se puede llamar muchas veces con la misma promesa. En otras palabras, una promesa se puede usar para agregar devoluciones de llamada.,
  • .then() debe devolver una nueva promesa, promise2.
  • Si onFulfilled() o onRejected() devuelve un valor x y x es una promesa, promise2 fija, con (suponer que el mismo estado y valor) x. De lo contrario, promise2 se cumplió con el valor de x.,
  • Si onFulfilled o onRejected lanza una excepción e, promise2 debe ser rechazada con la etiqueta e como la razón.
  • Si onFulfilled no es una función de y promise1 se ha cumplido, promise2 debe cumplir con el mismo valor como promise1.,
  • Si onRejected no es una función de y promise1 es rechazado, promise2 debe ser rechazado con la misma razón que el promise1.

encadenamiento de promesas

dado que .then() siempre devuelve una nueva promesa, es posible encadenar promesas con un control preciso sobre cómo y dónde se manejan los errores. Las promesas le permiten imitar el comportamiento del código síncrono normal try/catch.,

Al igual que el código síncrono, el encadenamiento dará como resultado una secuencia que se ejecuta en serie., En otras palabras, puede hacer:

fetch(url)
.then(process)
.then(save)
.catch(handleErrors)
;

Aquí hay un ejemplo de una cadena de promesas compleja con múltiples rechazos:

ejemplo de comportamiento de encadenamiento de promesas en CodePen

manejo de errores

tenga en cuenta que las promesas tienen tanto un éxito como un controlador de errores, y es muy común ver código que hace esto:

save().then(
handleSuccess,
handleError
);

pero ¿qué sucede si handleSuccess() lanza un error?, La promesa devuelta desde .then() será rechazada, pero no hay nada allí para detectar el rechazo, lo que significa que se tragará un error en su aplicación. Oops!

por esa razón, algunas personas consideran que el código anterior es un anti-patrón, y recomiendan lo siguiente, en su lugar:

save()
.then(handleSuccess)
.catch(handleError)
;

la diferencia es sutil, pero importante. En el primer ejemplo, se detectará un error originado en la operación save(), pero se tragará un error originado en la función handleSuccess().,

Without .catch(), an error in the success handler is uncaught.

In the second example, .catch() will handle rejections from either save(), or handleSuccess().

With .catch(), both error sources are handled., (fuente del diagrama)

Por supuesto, el error save() puede ser un error de red, mientras que el error handleSuccess() puede deberse a que el desarrollador olvidó manejar un código de estado específico. ¿Qué pasa si quieres manejarlos de manera diferente? Puede optar por manejarlos a ambos:

save()
.then(
handleSuccess,
handleNetworkError
)
.catch(handleProgrammerError)
;

lo que prefiera, recomiendo terminar todas las cadenas de promesas con un .catch(). Vale la pena repetir:

recomiendo terminar todas las cadenas de promesas con un .catch().,

¿Cómo cancelo una promesa?

una de las primeras cosas que los usuarios de New promise a menudo se preguntan es cómo cancelar una promesa. He aquí una idea: simplemente rechazar la promesa con «cancelado» como la razón. Si necesita lidiar con él de manera diferente a un error «normal», haga su ramificación en su controlador de errores.

Aquí hay algunos errores comunes que las personas cometen cuando ruedan su propia cancelación de promesa:

agregar .,cancel () a la promesa

agregar .cancel() hace que la promesa no sea estándar, pero también viola otra regla de las promesas: solo la función que crea la promesa debe ser capaz de resolver, rechazar o cancelar la promesa. Exponerlo rompe esa encapsulación y alienta a la gente a escribir código que manipula la promesa en lugares que no deberían saberlo. Evita los espaguetis y las promesas rotas.

olvidarse de limpiar

algunas personas inteligentes han descubierto que hay una manera de usar Promise.race() como mecanismo de cancelación., El problema con eso es que el control de cancelación se toma de la función que crea la promesa, que es el único lugar en el que puede realizar actividades de limpieza adecuadas, como borrar tiempos de espera o liberar memoria borrando referencias a datos, etc…

olvidarse de manejar una promesa de cancelación rechazada

¿sabía que Chrome lanza mensajes de advertencia por toda la consola cuando se olvida de manejar un rechazo de promesa? Oops!

excesivamente complejo

la propuesta TC39 retirada para la cancelación propuso un canal de mensajería separado para las cancelaciones., También utilizó un nuevo concepto llamado token de cancelación. En mi opinión, la solución habría inflado considerablemente la spec promesa, y la única característica que habría proporcionado que las especulaciones no apoyan directamente es la separación de rechazos y cancelaciones, que, IMO, no es necesario para empezar.

¿querrá hacer el cambio dependiendo de si hay una excepción o una cancelación? Sí, absolutamente. ¿Ese es el trabajo de la promesa? En mi opinión, no, no.,

repensar la cancelación de la promesa

generalmente, paso toda la información que la promesa necesita para determinar cómo resolver / rechazar / cancelar en el momento de la creación de la promesa. De esa manera, no hay necesidad de un método .cancel() en una promesa. Es posible que se pregunte cómo podría saber si va a cancelar o no en el momento de la creación de la promesa.

«Si aún no sé si cancelar o no, ¿Cómo sabré qué pasar cuando cree la promesa?,»

Si sólo hubiera algún tipo de objeto que pudiera estar en un valor potencial en el futuro… oh, espera.

el valor que pasamos para representar si cancelar o no podría ser una promesa en sí. Así es como podría verse:

Cancellable wait — pruébelo en CodePen

estamos usando la asignación de parámetros predeterminada para indicarle que no cancele por defecto. Eso hace que el parámetro cancel sea convenientemente opcional., Luego establecemos el tiempo de espera como lo hicimos antes, pero esta vez capturamos el ID del tiempo de espera para que podamos borrarlo más tarde.

utilizamos el método cancel.then() para manejar la cancelación y la limpieza de recursos. Esto solo se ejecutará si la promesa se cancela antes de que tenga la oportunidad de resolver. Si cancelas demasiado tarde, has perdido tu oportunidad. Ese tren ha salido de la estación.

Nota: Es posible que se pregunte para qué sirve la función noop(). La palabra noop significa no-op, que significa una función que no hace nada., Sin él, V8 lanzará advertencias: UnhandledPromiseRejectionWarning: Unhandled promise rejection. Es una buena idea manejar siempre los rechazos de promesas, incluso si su controlador es noop().

Abstracting Promise Cancellation

esto está bien para un wait() temporizador, pero podemos abstraer esta idea aún más para encapsular todo lo que tiene que recordar:

  1. rechazar la promesa de cancelación por defecto — no queremos cancelar o lanzar errores si no se pasa ninguna promesa de cancelación.
  2. recuerde realizar la limpieza cuando rechace cancelaciones.,
  3. recuerde que la limpieza onCancel podría generar un error, y ese error también necesitará ser manejado. (Tenga en cuenta que el manejo de errores se omite en el ejemplo de espera anterior — es fácil de olvidar!)

vamos a crear una utilidad promesa cancelable que puede utilizar para envolver cualquier promesa., Por ejemplo, para administrar las solicitudes de red, etc… La firma tendrá este aspecto:

speculation(fn: SpecFunction, shouldCancel: Promise) => Promise

El SpecFunction es igual que la función que iba a pasar en el Promise constructor, con una excepción — se necesita un onCancel() controlador:

SpecFunction(resolve: Function, reject: Function, onCancel: Function) => Void

tenga en cuenta que este ejemplo es sólo una ilustración para darle la esencia de cómo funciona. Hay algunos otros casos extremos que debe tener en cuenta., Por ejemplo, en esta versión, handleCancel se llamará si cancela la promesa después de que ya esté establecida.

he implementado una versión de producción mantenida de esto con casos extremos cubiertos como la biblioteca de código abierto, especulación.

usemos la abstracción mejorada de la biblioteca para reescribir la utilidad cancelable wait() de antes., Primero instale la especulación:

npm install --save speculation

Ahora puede importarlo y usarlo:

esto simplifica las cosas un poco, ya que no tiene que preocuparsenoop(), captura errores en suonCancel(), función u otros casos de borde. Esos detalles han sido extraídos por speculation(). Échale un vistazo y siéntete libre de usarlo en proyectos reales.,

Extras de la promesa nativa de JS

el objeto nativo Promise tiene algunas cosas adicionales que podrían interesarle:

  • Promise.reject() devuelve una promesa rechazada.
  • Promise.resolve() devuelve una promesa resuelta.
  • Promise.race() toma una matriz (o cualquier iterable) y devuelve una promesa que resuelve con el valor de la primera promesa resuelta en el iterable, o rechaza con la razón de la primera promesa que rechaza.,
  • Promise.all() toma una matriz (o cualquier iterable) y devuelve una promesa que se resuelve cuando todas las promesas en el argumento iterable se han resuelto, o rechaza con la razón de la primera promesa pasada que rechaza.

conclusión

Las promesas se han convertido en una parte integral de varios modismos en JavaScript, incluido el estándar WHATWG Fetch utilizado para la mayoría de las solicitudes ajax modernas, y el estándar de funciones asíncronas utilizado para hacer que el código asíncrono parezca sincrónico.,

Las funciones asíncronas son la etapa 3 en el momento de escribir este artículo, pero predigo que pronto se convertirán en una solución muy popular y muy utilizada para la programación asíncrona en JavaScript, lo que significa que aprender a apreciar las promesas será aún más importante para los desarrolladores de JavaScript en un futuro cercano.

por ejemplo, si está usando Redux, le sugiero que revise redux-saga: una biblioteca utilizada para administrar efectos secundarios En Redux que depende de funciones asíncronas en toda la documentación.,

espero que incluso los usuarios experimentados de promise tengan una mejor comprensión de lo que son las promesas y cómo funcionan, y cómo usarlas mejor después de leer esto.

Explore la serie

  • ¿Qué es un cierre?
  • ¿Cuál es la diferencia entre la clase y la herencia prototípica?
  • ¿Qué es una Función Pura?
  • ¿Qué es la composición de funciones?
  • ¿Qué es la Programación Funcional?
  • ¿Qué es una Promesa?
  • Habilidades

Deja una respuesta

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