What makes the type definition of `promise.all` particularly effective in this scenario?

While working on a question from type-challenges repository, I encountered this issue.

The code below fails in case 3:

declare function PromiseAll<A extends readonly unknown[]>(values: A): Promise<{
  -readonly [key in keyof A]: Awaited<A[key]>
}>

// test
const promiseAllTest3 = PromiseAll([1, 2, Promise.resolve(3)]) 
// expected Promise<[number, number, number]>, but got Promise<number[]>

However, simply changing the generic readonly unknown[] to (readonly unknown[]) | [] fixes the issue:

declare function PromiseAll<A extends (readonly unknown[]) | [] /* change here */>(values: A): Promise<{
  -readonly [key in keyof A]: Awaited<A[key]>
}>

// test
const promiseAllTest3 = PromiseAll([1, 2, Promise.resolve(3)]) // Promise<[number, number, number]>

The reason why adding a constraint of '[]' to generic 'A' affects case 3 is unclear to me. The connection between them is not obvious.

Furthermore, I discovered that the correct implementation is found in lib.es2015.promise.d.ts, which is provided in vscode:

// lib.es2015.promise.d.ts
/**
 * Creates a Promise that is resolved with an array of results when all of the provided Promises
 * resolve, or rejected when any Promise is rejected.
 * @param values An array of Promises.
 * @returns A new Promise.
 */
all<T extends readonly unknown[] | []>(values: T): Promise<{ -readonly [P in keyof T]: Awaited<T[P]> }>;

Upon testing, I found that this code works perfectly. By converting to Tuple, it maintains all types and their positions:

declare function PromiseAll<A extends any[]>(values: readonly [...A]): Promise<{
  [key in keyof A]: Awaited<A[key]>
}>

Answer №1

The TypeScript team has decided to infer array types over tuple types as the default behavior for array literals passed into a function.

If you hover over the function call, you can see this behavior in the tooltip:

declare function PromiseAll<A extends readonly unknown[]>(values: A): void

PromiseAll([1, 2, Promise.resolve(3)])
// function PromiseAll<(number | Promise<number>)[]>(...)

Instead of inferring the tuple type, A is inferred as the array type

(number | Promise<number>)[]
.

While this behavior usually works well, properly typing a Promise.all() function requires inferring the tuple type. There are different methods to achieve this, as explained in your question.


But why do both methods infer the tuple type? The explanation can be found in ahejlsberg's response to #31434

We only infer tuple types when the contextual type is or contains a tuple-like type

This crucial point is that (readonly unknown[]) | [] works because the union in the constraint now includes a tuple type [], prompting the compiler to infer a tuple type.

Using readonly [...A] also infers the tuple type thanks to the variadic tuple syntax introduced in 4.0. More information on inference with variadic tuple types can be found in PR/#39094.

When the contextual type of an array literal is a tuple type, a tuple type is inferred for the array literal. The type [...T], where T is an array-like type parameter, can conveniently be used to indicate a preference for inference of tuple types.

Answer №2

Essentially, it's a specific tuple type [number, number, number] instead of a general array type number[] because by adding ... | [] to the type, a literal type [] is included in the context of defining the return type, specifically within the Generic A.

This PR provides a thorough explanation and implementation of this concept: GitHub

Your code serves as a great example of the modification introduced in the PR:

The type assigned to an element in an array literal is the broader literal type of the expression unless the element has a contextually defined type that incorporates literal types.

The return type of PromiseAll is determined within the context of A. A utilizes a literal type in its definition, making it a "contextually defined type that incorporates literal types," thereby keeping the type specific as a tuple instead of widening to an array.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

"Capture input value changes and display the previous value when submitting a post. See an example of

Hi there! I'm facing 2 issues with my code, you can find a DEMO here When adding a product to the sale form, the input field for `description` changes for all products. Changing the input product in the sale does not reflect the change. I have shar ...

Issue with displaying entire object using Jest and console.dir

I'm having trouble displaying an error in my Jest test because it's not showing all the levels as expected. import util from 'util' describe('Module', () => { it('should display all levels WITHOUT util', () =& ...

Can someone provide a description for a field within typedoc documentation?

Here is the code snippet: /** * Description of the class */ export class SomeClass { /** * Description of the field */ message: string; } I have tested it on the TSDoc playground and noticed that there is a summary for the class, but not for it ...

Strategies for implementing searchbar filtering in Ionic3 and Angular5 data manipulation

I'm having trouble displaying product names on my search.html page based on the search bar input. I've tried using the Ionic searchbar component but it's not working. Can anyone help me with this issue? If there's an alternative solutio ...

Using Angular (along with Typescript) to showcase JSON data

I previously shared this query, but unfortunately, I didn't receive many helpful responses I have a JSON file that holds the following dataset: [{ "ID": 1030980, "Component": "Glikoza (Gluk)", "Result": "16", "Date": "20.10.2018" } ...

Creating definitions for generic static members within a third-party module

There is a 3rd party module with the following structure: export class Container{ static async action() { return {...} } constructor(params = {}) { // ... } async doSomething(params = {}) { // ... } } I am looking to de ...

Unable to navigate to a page called "meeting" in NextJS 13 due to issues with router.push not functioning correctly

import { Input, Button } from '@nextui-org/react'; import router from 'next/router'; import { SetStateAction, useEffect, useState } from 'react'; const SignIn = () => { const [errorMessage, setErrorMessage] ...

Encountered an error when creating my own AngularJS module: Unable to instantiate

Attempting to dive into TypeScript and AngularJS, I encountered a perplexing error after following a tutorial for just a few lines. It appears that there may be an issue with my mydModule? angular.js:68 Uncaught Error: [$injector:modulerr] Failed to inst ...

Create an interface object in TypeScript with no initial properties

I'm currently developing an app with angular 5 (TS) and I've encountered a problem while trying to initialize an empty object of my interface. The solution that I found and attempted is as follows: article: Article = {} as Article; However, ...

Unable to clear all checkboxes after deleting

In my application, there are 3 checkboxes along with a master checkbox that allows users to select or deselect all of them at once. Everything works fine with the master checkbox until I delete some rows from the table. After deleting data, I can check th ...

The parameter must be of type 'string', but you are attempting to assign a 'Promise<any>'

Starting a React App requires fetching the user's information from localStorage and initiating a socket connection with the server based on the user's id. However, when trying to pass the user's id to the socket function, TypeScript throws ...

Having trouble with image loading in NextJS after replacing an old image with a new one?

I have been attempting to swap out my current banner with different images to test if they work, but every image I try leads to an error when using next/image or even a simple <image> tag. The error message states that "The requested resource isn&apo ...

How to transition from using a CDN to NPM for implementing the Google Maps JavaScript MarkerClusterer?

Currently integrating Google Maps JavaScript MarkerClusterer from CDN, I am considering transitioning to the NPM version for Typescript checking in my JavaScript files. However, I am encountering difficulties understanding how to make this switch. The docu ...

Deactivating an emitted function from a child component in Angular 4

There is a main component: @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { funcBoo():void{ alert("boo"); //return fal ...

Building a dynamic and fast Vite project using "lit-ts" to create a visually appealing static website

I recently put together a project using Vite Lit Element Typescript and everything seemed to be running smoothly on the development server. However, when I tried running npm run build, only the compiled JS file was outputted to the /dist folder without any ...

Error: Unable to access property 'camera' as it is undefined

After implementing the Raycaster from Three js to detect collision following a MouseMove event, I encountered an error: Cannot read properties of undefined (reading 'camera') Here is the code snippet causing the issue: bindIFrameMousemove(if ...

Breaking down an object using rest syntax and type annotations

The interpreter mentions that the humanProps is expected to be of type {humanProps: IHumanProps}. How can I properly set the type for the spread operation so that humanPros has the correct type IHumanProps? Here's an example: interface IName { ...

IDE type inferences are wrong for the Polymorphic React component

declare const MyComponent = <A extends {id: bigint|number}>(props: MyProps<A>) => React.FC<{}> interface MyProps<A extends {id: number}> { data: A[] orderBy: keyof A } declare const x: { id: number, foo: string }[] const F ...

While running tslint in an angular unit test, an error was encountered stating 'unused expression, expected an assignment or function call'

Is there a method to resolve this issue without needing to insert an ignore directive in the file? Error encountered during command execution: ./node_modules/tslint/bin/tslint -p src/tsconfig.json --type-check src/app/app.component.spec.ts [21, 5]: unuse ...

Having trouble accessing specific results using Firestore's multiple orderBy (composite index) feature

I am facing an issue with a query that I run on various data types. Recently, one of the collections stopped returning results after I included orderBy clauses. getEntitiesOfType(entityType: EntityType): Observable<StructuralEntity[]> { const col ...