Planteamiento y contexto
Esta pregunta de código evalúa la programación de concurrencia en lugar de simplemente colocar cada promesa en Promise.all. El candidato debe definir un contrato para el rendimiento, el ordenamiento, la propagación de errores, la cancelación y la entrada vacía, y luego demostrar que nunca se excede el límite y que ningún elemento se consume dos veces.
Qué evalúa el entrevistador
- Si los workers fijos o un programador equivalente mantienen a lo sumo N tareas activas.
- Si los resultados se escriben según el índice de entrada para que el orden de finalización no altere el orden de salida.
- Si la semántica de fail-fast y recolección de errores es explícita, incluyendo las tareas que ya están en ejecución.
- Si se manejan correctamente un N inválido, una entrada vacía, excepciones sincrónicas, la cancelación y valores de mapper que no sean Promise.
Preguntas de aclaración para hacer
Confirma si las tareas pueden continuar después de la cancelación, si el modo fail-fast espera a la limpieza, si se devuelven resultados parciales y si mapper acepta un AbortSignal. Pregunta sobre reintentos, concurrencia dinámica y la preservación de la identidad del objeto de error original.
Estructura de respuesta en 30 segundos
Crearía no más de N workers que compartan un contador para el siguiente índice. Cada worker toma un índice, envuelve los retornos y excepciones sincrónicas con Promise.resolve y escribe según el índice. El modo fail-fast propaga el primer error observado y detiene nuevos despachos; el modo de recolección registra cada estado. La cancelación detiene la toma de nuevos índices y pasa la señal a mapper. El trabajo es O(items), el espacio para resultados y estados es O(items), y el trabajo activo está delimitado por O(N).
Análisis detallado paso a paso
1. Definir contratos de resultados y errores
Los resultados mantienen el orden de entrada. El modo de recolección devuelve el estado fulfilled o rejected con el valor o la razón original; undefined no puede usarse como marcador de éxito. El modo fail-fast devuelve el primer error observado, teniendo en cuenta que JavaScript no puede terminar forzosamente promesas ya iniciadas. Un mapper que acepte una señal puede cooperar con la cancelación.
2. Usar workers fijos y asignación de índices
Mantén el siguiente índice en un estado compartido. Cada worker itera hasta que el índice esté fuera de rango, se active la cancelación o fail-fast detenga el despacho. Incrementa antes de iniciar una tarea para que un elemento solo pueda ser tomado una vez. Utiliza min(N, items.length) workers para evitar promesas inactivas.
3. Manejar excepciones sincrónicas y el orden de finalización
Captura las excepciones sincrónicas de mapper y normaliza tanto los valores como las promesas con Promise.resolve. Escribe el éxito o el fallo en el índice original; nunca uses push desde las funciones de retrollamada (callbacks) de finalización. Tan pronto como un worker termina, toma el siguiente índice, manteniendo el rendimiento sin incrementar el trabajo activo.
4. Propagar la cancelación y detener el despacho
Si la señal ya fue abortada, rechaza o devuelve el error de cancelación documentado antes de comenzar el trabajo. Las tareas en ejecución reciben la misma señal. El programador espera a que los workers salgan antes de resolver su promesa principal, de modo que quienes llaman a la función no observen la finalización mientras un bucle en segundo plano continúa despachando tareas. La cancelación y los errores de negocio requieren causas diferenciables.
5. Verificar casos límite y complejidad
Prueba con un arreglo vacío, N igual a cero, N mayor que la entrada, valores y excepciones sincrónicas, retrasos variados, el primer fallo, múltiples fallos y cancelación sobre la marcha. Utiliza un contador activo para asegurar que nunca exceda N y un hook para confirmar que cada índice se ejecute una sola vez. Las llamadas a mapper son O(items), el espacio para resultados es O(items) y la concurrencia del programador es O(N).
Respuesta de ejemplo sólida
Valido que N sea un entero positivo, creo min(N, items.length) workers y comparto un contador para el siguiente índice. Un worker toma un índice, llama a mapper mediante Promise.resolve para que los valores y excepciones sincrónicas sigan la misma ruta, y escribe el resultado por índice. El modo fail-fast establece una bandera para detener el despacho y propaga el primer error; el modo de recolección conserva cada estado. La cancelación impide tomar nuevos índices y pasa la señal a los mappers activos, esperando luego a que los workers salgan. Las pruebas cubren retrasos desordenados, errores sincrónicos, entrada vacía, límites de N y cancelación, con una aserción de que el trabajo activo nunca excede N.
Errores comunes
- Llamar a
Promise.all(items.map(mapper))e iniciar todas las tareas a la vez. - Usar push para los resultados, haciendo que la salida siga el orden de finalización en lugar del orden de entrada.
- Capturar únicamente rechazos de promesas y omitir excepciones sincrónicas lanzadas por mapper.
- Continuar despachando tareas después de fail-fast, o asumir que reject cancela automáticamente el trabajo en ejecución.
- Tratar la cancelación como un error de negocio ordinario, impidiendo que el invocador elija el comportamiento de reintento.
- Omitir la validación de N, de la entrada vacía y de valores ordinarios de mapper que no sean Promise.
Preguntas de seguimiento y respuestas
¿Por qué no iniciar recursivamente el siguiente elemento?
La recursión puede expresar una cadena serial, pero también debe gestionar la concurrencia, los errores y la cancelación. Los workers fijos hacen que la demostración de tareas activas sea más clara y evitan la recursión profunda o el despacho duplicado.
¿Qué sucede con las tareas que ya están ejecutándose durante un fail-fast?
Las promesas de JavaScript no tienen una primitiva general de terminación forzada. Detén nuevos despachos y llama a abort cuando el mapper coopere con AbortSignal; de lo contrario, deja que las tareas terminen sin escribir en un resultado ya resuelto externamente.
¿Cómo debería el modo de recolección representar los errores?
Devolviendo un objeto de estado por elemento que distinga entre fulfilled y rejected mientras preserva el valor o la razón original. Un valor centinela es inseguro porque un valor exitoso podría ser en sí mismo undefined o null.
¿Cómo ajustarías la concurrencia dinámicamente?
Rastreando el máximo configurado por separado del trabajo activo y agregando o deteniendo la toma de tareas en límites seguros. El ajuste dinámico incrementa la complejidad de las pruebas y la demostración, por lo que se recomienda usar un límite fijo a menos que el requerimiento exija explícitamente adaptación.