admin管理员组

文章数量:1410682

I created a Fetch function to consume a JSON API and have defined types for the JSON object. I am confused about how to define the return type for the getCurrentJobAPI function since I do a bunch of .then() afterwards. Is the return value the last .then()? In my code, the last .then() is a setState, so what would the type be for that?

    getCurrentJobAPI = (): {} => {

        const url: string = `dummy_url&job_id=${this.props.currentJob}`;

        return fetch(url, {credentials: 'include'})
            .then((response) => {
                return response.json();
            })
            .then((json: CurrentJob) => {
                console.log(json);
                const location = json.inventoryJob.location;
                const ref_note = json.inventoryJob.note;
                const id = json.inventoryJob.id;
                const models = json.inventoryJobDetails.map((j) => {
                    return Object.assign({}, {
                        code: j.code,
                        qty: j.qty
                    })
                });
                this.setState({ currentCodes: models, location: location, ref_note: ref_note, id: id})
                return json
            })
            .then((json: CurrentJob) => {
            const barcodes = json.inventoryJob.history;
            if (barcodes.length > 0) {
                this.setState({apiBarcodes: barcodes})
            }
            this.calculateRows();
            this.insertApiBarcodes();
            this.setState({ initialLoad: true });
            })
    };

UPDATE:

Although I understand that I am supposed to define Promise<type> as the return value of getCurrentJobAPI (see Gilad's answer and ments), I am still unsure why I can't write Promise<CurrentJob> if the Fetch resolves as the JSON response.

[I have condensed my .then() statements per loganfsmyth's remondation.]

Here are the type definitions for CurrentJob:

type Job = {
    user_id: number,
    status: 'open' | 'closed',
    location: 'string',
    history: {[number]: string}[],
    note: string,
} & CommonCurrentJob;

type JobDetails = {
    iaj_id: number,
    code: number,
} & CommonCurrentJob;


type CommonCurrentJob = {
    id: number,
    qty: number,
    qty_changed: number,
    created_at: string,
    updated_at: string
}

I created a Fetch function to consume a JSON API and have defined types for the JSON object. I am confused about how to define the return type for the getCurrentJobAPI function since I do a bunch of .then() afterwards. Is the return value the last .then()? In my code, the last .then() is a setState, so what would the type be for that?

    getCurrentJobAPI = (): {} => {

        const url: string = `dummy_url&job_id=${this.props.currentJob}`;

        return fetch(url, {credentials: 'include'})
            .then((response) => {
                return response.json();
            })
            .then((json: CurrentJob) => {
                console.log(json);
                const location = json.inventoryJob.location;
                const ref_note = json.inventoryJob.note;
                const id = json.inventoryJob.id;
                const models = json.inventoryJobDetails.map((j) => {
                    return Object.assign({}, {
                        code: j.code,
                        qty: j.qty
                    })
                });
                this.setState({ currentCodes: models, location: location, ref_note: ref_note, id: id})
                return json
            })
            .then((json: CurrentJob) => {
            const barcodes = json.inventoryJob.history;
            if (barcodes.length > 0) {
                this.setState({apiBarcodes: barcodes})
            }
            this.calculateRows();
            this.insertApiBarcodes();
            this.setState({ initialLoad: true });
            })
    };

UPDATE:

Although I understand that I am supposed to define Promise<type> as the return value of getCurrentJobAPI (see Gilad's answer and ments), I am still unsure why I can't write Promise<CurrentJob> if the Fetch resolves as the JSON response.

[I have condensed my .then() statements per loganfsmyth's remondation.]

Here are the type definitions for CurrentJob:

type Job = {
    user_id: number,
    status: 'open' | 'closed',
    location: 'string',
    history: {[number]: string}[],
    note: string,
} & CommonCurrentJob;

type JobDetails = {
    iaj_id: number,
    code: number,
} & CommonCurrentJob;


type CommonCurrentJob = {
    id: number,
    qty: number,
    qty_changed: number,
    created_at: string,
    updated_at: string
}
Share edited Nov 20, 2017 at 22:42 Avi Kaminetzky asked Nov 20, 2017 at 19:17 Avi KaminetzkyAvi Kaminetzky 1,5382 gold badges21 silver badges45 bronze badges 5
  • 1 The return value is the entire promise chain. You could technically take the result of getCurrentJobAPI() and attach more then and catch type things to it. Is that what you were asking? Otherwise I'm not sure what to say. You can't return anything other than the promise itself, because otherwise it would no longer be asynchronous. – Matt Fletcher Commented Nov 20, 2017 at 19:22
  • If the return value is the entire promise chain, how can I define the type of Promise in Flow? Where is the documentation for such a case? – Avi Kaminetzky Commented Nov 20, 2017 at 19:28
  • 1 You can't. That's not how async would work. If you tried running code after the promise chain, it would possibly run before the promise gets to the end. So you can't think of it as returning a value, because that is synchronous – Matt Fletcher Commented Nov 20, 2017 at 19:30
  • Sorry, just realised that I haven't understood your question. My bad – Matt Fletcher Commented Nov 20, 2017 at 19:34
  • 1 Not an answer to your question, but almost none of those .thens are needed. You use .then either to return another promise, or as the final handler in a promise chain. Most of your intermediate .then handlers just end up returning undefined so you could collapse them into the first .then. – loganfsmyth Commented Nov 20, 2017 at 20:00
Add a ment  | 

2 Answers 2

Reset to default 5

So first off, a disclaimer, I am a TypeScript user but I find that this question is actually applicable to both languages and has the same answer.

I created a Fetch function to consume a JSON API and have defined types for the JSON object. I am confused about how to define the return type for the getCurrentJobAPI function since I do a bunch of .then() afterwards. Is the return value the last .then()? In my code, the last .then() is a setState, so what would the type be for that?

TL;DR: Promise<void> (see note). As you suspect, this is in fact the return type of the last top-level .then in the promise chain.

Now lets dig a bit deeper

Here is your example, reworked very slightly to leverage type inference instead of annotating callback parameters that are declared as any by their receivers.

As an aside, these callback parameter annotations amount to unsafe implicit casts, or type assertions as we call them in TypeScript, and they lie about the shape of the code. They look like this

declare function takesFn(fn: (args: any) => any): void;

So I have minimized these since they form a subtle trap

// @flow
import React from 'react';

type CurrentJob = {
    inventoryJob: Job,
    inventoryJobDetails: JobDetails[]
}

export default class A extends React.Component<{currentJob:JobDetails}, any> {

  getCurrentJobAPI: () => Promise<void> = () => {

        const url = `dummy_url&job_id=${String(this.props.currentJob)}`;

        return fetch(url, {credentials: 'include'})
            .then(response => {
                return (response : {json(): any}).json();
            }) // --> Promise<any>
            .then(json => {

                const currentJob = (json: CurrentJob); // make the assumption explicit.

                console.log(currentJob);

                const {location, id, note: ref_note} = currentJob.inventoryJob;

                const currentCodes = currentJob.inventoryJobDetails
                  .map(({code, qty}) => ({
                    code,
                    qty
                  }));

                this.setState({currentCodes, location, ref_note, id});
                return currentJob;
            }) // --> Promise<CurrentJob>
            .then(currentJob => {
                const apiBarcodes = currentJob.inventoryJob.history;
                if (apiBarcodes.length > 0) {
                    this.setState({apiBarcodes});
                }
                this.setState({initialLoad: true});
            }); // --> Promise<void>
    };
}

So I am making assertions about the promises in each then call above but those assertions are all validated by type inference with the exception of the initial type cast on the response value.

As further evidence, if we remove the type declaration from the getCurrentJobAPI property of A, flow will infer that its type is in fact Promise<void>.

Bonus: simplifying with async/await. I've used several ESNext features above to shorten the code and make it a bit more pleasant, but we can leverage a specific feature, async/await to make it easier to understand control flow and types in Promise based code.

Consider this revision.

// @flow
import React from 'react';

type CurrentJob = {
    inventoryJob: Job,
    inventoryJobDetails: JobDetails[]
}

export default class A extends React.Component<{currentJob:JobDetails}, any> {
    getCurrentJobAPI = async () => {

        const url = `dummy_url&job_id=${String(this.props.currentJob)}`;

        const response = await fetch(url, {credentials: 'include'});
        const json = await response.json();
        const currentJob = (json: CurrentJob); // make the assumption explicit.

        console.log(currentJob);

        const {location, id, note: ref_note} = currentJob.inventoryJob;

        const currentCodes = currentJob.inventoryJobDetails.map(({code, qty}) => ({
            code,
            qty
        }));

        this.setState({currentCodes, location, ref_note, id});


        const apiBarcodes = currentJob.inventoryJob.history;
        if (apiBarcodes.length > 0) {
            this.setState({apiBarcodes});
        }
        this.setState({initialLoad: true});

    };
}

Clearly, this is a void function. It has no return statements. However, as an async function, it inherently returns a Promise, just as it did when written as an explicit Promise chain.

Note: void is a construct that has been found useful in Flow and TypeScript to represent the semantic intent of function that do not return values but in reality such functions actually return undefined because, well, this is JavaScript. Flow does not seem to recognize undefined as a type, but under TypeScript, the function could equally be annotated as returning Promise<undefined>. Irregardless, Promise<void> is preferable thanks to the clarity of intent it provides.

Remarks: I worked through this using a bination of https://flow/try and the flow binary for Windows. The experience on Windows is really terrible and hopefully it will improve.

When chaining then's, the result will always be a promise. When calling then, the return value is another promise, otherwise chaining then's wouldn't have been possible. You can see that easily by using console.log() surrounding the entire chain.

本文标签: javascriptFlow Types with Promises (Fetch39s)Stack Overflow