«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:
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()
yonRejected()
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()
nionRejected()
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()
oonRejected()
devuelve un valorx
yx
es una promesa,promise2
fija, con (suponer que el mismo estado y valor)x
. De lo contrario,promise2
se cumplió con el valor dex
., - Si
onFulfilled
oonRejected
lanza una excepcióne
,promise2
debe ser rechazada con la etiquetae
como la razón. - Si
onFulfilled
no es una función de ypromise1
se ha cumplido,promise2
debe cumplir con el mismo valor comopromise1
., - Si
onRejected
no es una función de ypromise1
es rechazado,promise2
debe ser rechazado con la misma razón que elpromise1
.
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:
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()
.,
In the second example, .catch()
will handle rejections from either save()
, or handleSuccess()
.
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:
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 esnoop()
.
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:
- rechazar la promesa de cancelación por defecto — no queremos cancelar o lanzar errores si no se pasa ninguna promesa de cancelación.
- recuerde realizar la limpieza cuando rechace cancelaciones.,
- 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: