打字稿添加“ | null”以返回Promise.all类型。

时间:2019-12-20 17:53:12

标签: reactjs typescript

我有一个异步TS函数,该函数发出一个请求并将响应数据转换为boolean并返回,但在调用函数VS Code告诉我,当我返回时,返回值为boolean | nullPromise.all拨打电话。这是代码:

功能:

import apiAxios from "../apiAxios";

export default async function doesAssignmentHaveTakes(
  assignmentId: number
): Promise<boolean> {
  const response = await apiAxios.get(`/assignments/${assignmentId}/has-takes`);

  return !!response.data;
}

呼叫者:

import React, { FC, useState, useCallback } from "react";
import styled from "styled-components/macro";
import AssignmentForm, {
  Props as AssignmentFormProps,
  Value as AssignmentFormValue
} from "./AssignmentForm";
import useAsyncEffect from "../utils/useAsyncEffect";
import getAssignmentById from "../api/assignments/getAssignmentById";
import doesAssignmentHaveTakes from "../api/assignmentTakes/doesAssignmentHaveTakes";

interface Props extends AssignmentFormProps {
  assignmentId: number;
  onSubmit(value: Value): any;
}

export interface Value extends AssignmentFormValue {
  assignmentId: number;
}

const EditAssignmentForm: FC<Props> = props => {
  const { assignmentId, onSubmit, ...rest } = props;
  const [showEditWarning, setShowEditWarning] = useState(false);
  const [initialValue, setInitialValue] = useState<AssignmentFormValue | null>(
    null
  );

  useAsyncEffect(
    async isCancelled => {
      const [fetchedAssignment, hasTakes] = await Promise.all([
        getAssignmentById(assignmentId),
        doesAssignmentHaveTakes(assignmentId)
      ]);

      if (!fetchedAssignment) {
        // TODO: Alert parent component?
        return;
      }

      const value: Value = {
        assignmentId: fetchedAssignment.id,
        assignment: {
          title: fetchedAssignment.title,
          subTitle: fetchedAssignment.subTitle
        },
        sets: fetchedAssignment.sets
          .map(set => ({
            id: set.id,
            index: set.index,
            questions: set.questions
              .map(question => ({
                id: question.id,
                index: question.index,
                isPractice: question.isPractice,
                questionText: question.questionText,
                inputType: question.inputType,
                questionImage: question.questionImage,
                sampleResponseText: question.sampleResponseText,
                sampleResponseImage: question.sampleResponseImage
              }))
              .sort((a, b) => a.index - b.index),
            learningTarget: set.learningTarget,
            isExampleCorrect: set.isExampleCorrect,
            exampleImage: set.exampleImage,
            character: set.character
          }))
          .sort((a, b) => a.index - b.index)
      };

      if (!isCancelled()) {
        setInitialValue(value);
        setShowEditWarning(hasTakes);
      }
    },
    [assignmentId]
  );

  const handleSubmit = useCallback(
    (value: AssignmentFormValue) => {
      onSubmit({
        ...value,
        assignmentId
      });
    },
    [onSubmit, assignmentId]
  );

  if (!initialValue) {
    // Loading...
    return null;
  }

  return (
    <AssignmentForm
      {...rest}
      initialValue={initialValue}
      onSubmit={handleSubmit}
    />
  );
};

export default styled(EditAssignmentForm)``;

问题的具体内容:

  const [fetchedAssignment, hasTakes] = await Promise.all([
    getAssignmentById(assignmentId),
    doesAssignmentHaveTakes(assignmentId)
  ]);

setShowEditWarning(hasTakes);

TS错误:

TypeScript error in /Users/james/projects/math-by-example/client/src/components/EditAssignmentForm.tsx(71,28):
Argument of type 'boolean | null' is not assignable to parameter of type 'SetStateAction<boolean>'.
  Type 'null' is not assignable to type 'SetStateAction<boolean>'.  TS2345

    69 |       if (!isCancelled()) {
    70 |         setInitialValue(value);
  > 71 |         setShowEditWarning(hasTakes);
       |                            ^
    72 |       }
    73 |     },
    74 |     [assignmentId]

以及VS Code中的错误的一些屏幕截图

VS Code type tooltip

VS Code error tooltip

TS为什么将null添加到已解析的Promise.all类型中?

4 个答案:

答案 0 :(得分:7)

解决方案是将as const添加到传递给Promise.all的数组中。

说明

问题不在于键入Promise.all还是编译器中的错误。问题是TypeScript在默认情况下对数组执行的操作。考虑一下:

const q = [1, "a"];

q的默认类型推断为(string | number)[]。即使您将数字作为第一个位置,将字符串作为第二个位置,TypeScript也会推断 all 位置可以是字符串或数字。如果希望TypeScript将数组视为元组并为每个位置分配尽可能窄的类型,则可以执行以下操作:

const q = [1, "a"] as const;

TS将为此数组推断readonly [1, "a"]的类型。因此q在第一个位置只能有数字1,在第二个位置只能有字符串"a"。 (它也是只读的,但这是一个附带问题。)这是在TypeScript 3.4中引入的。

好的,这与您的案件有什么关系?当您将数组传递给Promise.all时,TypeScript使用的是我在第一个示例中显示的类型推断。 Promise.all看到一个数组,其中每个项目都可以采用该项目可以采用的所有值的并集。如果使用as const,则推论将类似于我上面显示的第二种情况,这将相应地反映在Promise.all的类型中。同样,Promise.all的键入没有没问题。它可以满足以下条件:输入错误,输入错误。

这是一个插图(也在playground中):

async function fa(): Promise<string | null> { return "foo"; }

async function fb(): Promise<boolean> { return true; }

async function main(): Promise<void> {
    let a: string | null;
    let b: boolean;

    // Remove this "as const" and the code won't compile.
    [a, b] = await Promise.all([fa(), fb()] as const);

    console.log(a, b);
}

main();

答案 1 :(得分:2)

此问题已从ts 3.9+(release note)解决,升级到3.9,您将不会看到此错误。

答案 2 :(得分:0)

问题在于Promise.alltake a look的类型定义。 .all的返回类型总是尝试使该数组的并集产生通用类型。

其他函数getAssignmentById可能返回null,因此Promise.all会推断出返回类型为[something | null, boolean | null]。我不确定TS编译器可能有错误。我在相同的条件下制作了一个playground,以查看实践中的推论,看看它如何推导Promise构造函数上的泛型类型,然后从{{1}的返回类型中删除null },然后再次查看funcB类型...它的行为符合预期。

答案 3 :(得分:0)

同意佩德罗的回答,即Promise.all不会开箱即用地处理不同的退货类型。

您可以尝试这样声明Promise的返回类型:

const [fetchedAssignment, hasTakes] = await Promise.all<string | null, boolean>([
    getAssignmentById(assignmentId),
    doesAssignmentHaveTakes(assignmentId)
]);