Типы потоков с обещаниями (Fetch's)

Я создал функцию Fetch для использования JSON API и определил типы для объекта JSON. Я запутался в том, как определить тип возвращаемого значения для функции getCurrentJobAPI, так как после этого я делаю кучу .then(). Является ли возвращаемое значение последним .then()? В моем коде последний .then() — это setState, так что же это за тип?

    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 });
            })
    };

ОБНОВЛЕНИЕ:

Хотя я понимаю, что должен определить Promise<type> как возвращаемое значение getCurrentJobAPI (см. ответ Гилада и комментарии ), я все еще не уверен, почему я не могу написать Promise<CurrentJob>, если Fetch разрешается как ответ JSON.

[Я сократил свои операторы .then() в соответствии с рекомендацией loganfsmyth.]

Вот определения типа для 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
}

person Avi Kaminetzky    schedule 20.11.2017    source источник
comment
Возвращаемое значение — это вся цепочка обещаний. Технически вы можете взять результат getCurrentJobAPI() и присоединить к нему дополнительные элементы типа then и catch. Это то, что вы спрашивали? В противном случае я не уверен, что сказать. Вы не можете вернуть ничего, кроме самого промиса, потому что иначе он больше не был бы асинхронным.   -  person Matt Fletcher    schedule 20.11.2017
comment
Если возвращаемое значение представляет собой всю цепочку обещаний, как я могу определить тип обещания в Flow? Где документация на такой случай?   -  person Avi Kaminetzky    schedule 20.11.2017
comment
Вы не можете. Это не то, как асинхронность будет работать. Если вы попытаетесь запустить код после цепочки промисов, возможно, он запустится до промиса до конца. Таким образом, вы не можете думать об этом как о возврате значения, потому что это синхронно   -  person Matt Fletcher    schedule 20.11.2017
comment
Извините, только что понял, что не понял вашего вопроса. Виноват   -  person Matt Fletcher    schedule 20.11.2017
comment
Не ответ на ваш вопрос, но почти ни один из этих .then не нужен. Вы используете .then либо для возврата другого промиса, либо в качестве последнего обработчика в цепочке промисов. Большинство ваших промежуточных обработчиков .then просто возвращают undefined, чтобы вы могли свернуть их в первый .then.   -  person loganfsmyth    schedule 20.11.2017


Ответы (2)


Итак, во-первых, отказ от ответственности, я пользователь TypeScript, но я считаю, что этот вопрос на самом деле применим к обоим языкам и имеет один и тот же ответ.

Я создал функцию Fetch для использования JSON API и определил типы для объекта JSON. Я запутался в том, как определить тип возвращаемого значения для функции getCurrentJobAPI, так как после этого я делаю кучу .then(). Является ли возвращаемое значение последним .then()? В моем коде последний .then() — это setState, так что же это за тип?

TL;DR: Promise<void> (см. примечание). Как вы подозреваете, на самом деле это возвращаемый тип последнего верхнего уровня .then в цепочке промисов.

Теперь давайте копнем немного глубже

Вот ваш пример, очень немного переработанный, чтобы использовать вывод типа вместо аннотирования параметров обратного вызова, которые объявлены их получателями как any.

Кроме того, эти аннотации параметров обратного вызова представляют собой небезопасные неявные приведения или утверждения типа, как мы называем их в TypeScript, и они лгут о форме кода. Они выглядят так

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

Поэтому я свел их к минимуму, так как они образуют скрытую ловушку

// @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>
    };
}

Итак, я делаю утверждения об обещаниях в каждом вызове then выше, но все эти утверждения проверяются выводом типа, за исключением начального типа, приведенного к значению ответа.

В качестве дополнительного доказательства, если мы удалим объявление типа из свойства getCurrentJobAPI для A, поток сделает вывод, что его тип на самом деле Promise<void>.

Бонус: упрощение с помощью async/await. Я использовал несколько функций ESNext выше, чтобы сократить код и сделать его более приятным, но мы можем использовать конкретную функцию, async/await, чтобы упростить понимание потока управления и типов в коде на основе Promise.

Рассмотрим эту ревизию.

// @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});

    };
}

Ясно, что это функция void. В нем нет операторов return. Однако, как функция async, она по своей сути возвращает Promise, точно так же, как если бы она была написана как явная цепочка Promise.

Примечание. void — это конструкция, которая оказалась полезной в Flow и TypeScript для представления семантического назначения функции, которая не возвращает значений, но на самом деле такие функции фактически возвращают undefined, потому что это JavaScript. Похоже, что Flow не распознает undefined как тип, но в TypeScript функция может быть также аннотирована как возвращающая Promise<undefined>. Тем не менее, Promise<void> предпочтительнее благодаря ясности намерений, которую он обеспечивает.

Примечания. Я работал над этим, используя комбинацию https://flow.org/try и двоичный файл потока для Windows. Опыт работы с Windows действительно ужасен, и, надеюсь, он улучшится.

person Aluan Haddad    schedule 27.11.2017

При цепочке then результатом всегда будет обещание. При вызове then возвращаемое значение является другим обещанием, иначе цепочка then была бы невозможна. Вы можете легко увидеть это, используя console.log(), окружающий всю цепочку.

person Gilad Bar    schedule 20.11.2017
comment
Итак, как именно мне определить возвращаемый тип моей функции? Определяю ли я тип, который охватывает всю цепочку? - person Avi Kaminetzky; 20.11.2017
comment
Тип возвращаемого значения в этом случае будет Promise. - person Gilad Bar; 20.11.2017
comment
Отвечает ли это на ваш вопрос? stackoverflow.com/questions/38730646 / - person Gilad Bar; 20.11.2017
comment
Еще нет, сам Promise требует тип? Например, Promise‹любой› или что-то подобное? - person Avi Kaminetzky; 20.11.2017
comment
@AvremelKaminetzky Да, в Flow Promise — это тип, генерирующий тип (т. е. он принимает тип s в качестве параметра и внутренне генерирует базовый тип, который не используется), поэтому вам нужно указать тип для обещания, например Promise<string> для обещания, которое разрешается с помощью строка. Вы также можете использовать Promise<*>, чтобы позволить Flow понять это, и Promise<any>, чтобы запретить Flow проверять фактический тип промиса (не рекомендуется, any следует использовать только тогда, когда у вас нет другого варианта). - person Mörre; 20.11.2017
comment
@Mörre my q - это тип обещания, если оно развивается по цепочке .then(). - person Avi Kaminetzky; 20.11.2017
comment
@AvremelKaminetzky Каким бы ни было обещание! Конечно, это требует, чтобы вы действительно понимали свою цепочку промисов — т. е. минус-типы, только саму вещь? Если вы не полностью получаете обещания, трудно говорить о типах. Сначала вы должны понять свой код. getCurrentJobAPI решает, что возвращает последний элемент цепочки обещаний. Тип этой вещи - это тип промиса - поскольку он всегда возвращает промис, вам просто нужно выяснить, с чем это промис разрешается. - person Mörre; 20.11.2017
comment
У меня есть console.log мой код. На первом этапе выход Promise { "pending" }. На последнем этапе выводится мой объект JSON (CurrentJob). Если это так, окончательное решение Promise — CurrentJob. Почему я получаю сообщение об ошибке при написании Promise<CurrentJob>? - person Avi Kaminetzky; 21.11.2017
comment
Потому что тип json, а не CurrentJob - person Gilad Bar; 21.11.2017
comment
Но CurrentJob — это определение типа моего объекта Json? Я должен что-то упустить. - person Avi Kaminetzky; 21.11.2017
comment
@AvremelKaminetzky JSON — это строковый формат. У вас либо есть JSON string(!!!) - либо у вас есть объект. Не существует такой вещи, как объект JSON, на самом деле этот термин сам по себе является противоречием, как лед-огонь. Вы можете кодировать объект как строку JSON. Но тогда у вас есть строка. Который содержит объект, но только если вы его расшифруете. - person Mörre; 21.11.2017