Как использовать / перерабатывать вложенные обещания? - PullRequest
0 голосов
/ 16 января 2020

Я использую Promises в своем приложении TypeScript для извлечения моих данных из наилучшего возможного источника. Сначала нужно попробовать localStorage, а затем вызов API на моем сервере. Я не уверен, является ли этот подход лучшим, поэтому комментарии приветствуются.

loadData(): Promise<void> {
    return this.loadFromLocalStorage()
        .catch(() => {
            this.loadFromApi();
        })
}

loadFromLocalStorage() : Promise<void> {
    return new Promise((resolve, reject) => {
        if( !this.isSupported(() => localStorage) ) reject("Localstorage is not supported");
        data = JSON.parse( localStorage.getItem('metadata') );
        if( !data || !data.date ) {console.log("no data"); reject("Not all data was present");}
        this.setData(data, false).then(() => {resolve()});
    });
}

loadFromApi() : Promise<void> {
    return new Promise((resolve, reject) => {
        this.http.get<{date?:string,ddi?:number,stoff?:number}>(this.apiUrl+'meta.json')
        .subscribe(data => {
            this.setData(data, true).then(() => resolve());
        },
        () => {
            reject("API call failed");
        });
    });
}

setData(data, saveToLocal: boolean) : Promise<void> {
    return new Promise((resolve) => {
        let promises: Promise<Pilot|void>[] = [];
        if (data.ddi) {
            promises.push( this.pilotService.getById(data.ddi).then(ddi => this.ddi = ddi) );
        }
        // There's more promises by the way, this is simplified
        Promise.all(promises).then(function() {
            resolve();
        });
    });
}

Теперь я знаю, что здесь есть какая-то ошибка, но основные вопросы: как мне справиться с возвратом обещание внутри обещания? Это даже необходимо? Я пытался найти решение, чтобы сгладить ситуацию, но не смог найти решение, которое учитывает, что некоторые действия не должны выполняться при сбое первого обещания (выборки из localStorage).

Ответы [ 3 ]

1 голос
/ 16 января 2020

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

  1. Обратите внимание, что асин c функции всегда возвращают Promises. По сути, вы просто оборачиваете каждое тело функции в Promise, чтобы они возвращали единицу, но это не нужно, если вы включаете в себя функцию asyn c.
  2. вместо использования логических значений для индикации состояния успеха / неудачи. Я вместо этого использовал отклоненные Обещания, так как он более идиоматический c в JavaScript и с помощью async / await вы можете написать это интуитивно «синхронно» (throw в асинхронной функции c приводит к отклонению возвращаемого Обещания )
  3. Когда вы можете вернуть значение, указывающее на успех, я настоятельно рекомендую сделать это - независимо от того, используете ли вы его на самом деле, это не имеет большого значения, но если вам это нужно, это может упростить существующий код.
  4. Вы можете использовать try/catch при использовании await в качестве метода обработки await ed Отклонения Promise, это делает код очень интуитивно понятным
  5. Ненависть стиля: использование точек с запятой несовместимо. Либо точка с запятой, либо точка с запятой не годится, но вы должны придерживаться одной или другой. Это улучшает читабельность, и если кто-то еще коснется вашего кода, у него не возникнет вопросов о том, когда использовать точку с запятой.

async loadData(): Promise<void> {
    try {
        return await this.loadFromLocalStorage(); //await this one to catch rejections
    } catch(e) {
        console.log(e.message); //handle error
        return this.loadFromApi(); //await or don't await this one, same result
    }
}

async loadFromLocalStorage() : Promise<Pilot[]> {
    if( !this.isSupported(() => localStorage) ) throw new Error("Not Supported");
    //if JSON.parse errors, the promise will also convert the exception to a rejection
    data = JSON.parse( localStorage.getItem('metadata') );
    if( !data || !data.date ) throw new Error("No Data");
    //this function returns a promise resolving with whatever `setData` resolves with
    //or rejects if `setData` rejects
    return this.setData(data, false);
}

//this one still needs to return a promise as it wraps a .subscribe()
loadFromApi() : Promise<(Pilot|void)[]> {
    return new Promise((resolve, reject) => {
        this.http.get<{date?:string,ddi?:number,stoff?:number}>(this.apiUrl+'meta.json')
            .subscribe( data => {
                resolve(this.setData(data, true)); //resolve with the data
            },
            reject); //if there's an error, just reject with the error
    });
}

async setData(data, saveToLocal: boolean) : Promise<(Pilot|void)[]> {
    let promises: Promise<Pilot|void>[] = [];
    if (data.ddi) {
        promises.push( this.pilotService.getById(data.ddi).then(ddi => this.ddi = ddi) );
    }
    //Promise.all will reject if any one of the inner promises rejects
    //To circumvent this, add a .catch() to the inner promises
    return Promise.all(promises);
}

Этот код соответствует более идиоматическому c использованию Promises и асинхронно / жду. Каждая функция либо разрешает со значимым значением в случае успеха, либо отклоняет со значительной ошибкой, если по какой-либо причине происходит сбой.

Из-за асинхронности / ожидания все ошибки также приводят к тому, что отклонение «заполняет» стек до самого вызывающего, т. Е. Если что-то пойдет не так в loadFromApi() Обещание отклонит с ошибкой, которая затем приводит к отклонению loadData() с той же самой ошибкой, открывая ее для функции, вызвавшей loadData(), и позволяя вам инкапсулировать обработку ошибок для этого API данных (любые ошибки, вызванные любой внутренней функцией, могут " пузыря "до точки входа loadData() и будет обрабатываться, как вы будете sh)

1 голос
/ 16 января 2020

давайте нарисуем ваш API

           loadData
          / 
         / 
        /
       *
loadFromLS ------* loadFromApi
       \               /
        \             /
         \           /
          \         /
           \       /
            \     /
             *   *
            setData

Таким образом, ваш API является однонаправленным, (ничего не идет вверх)


Но ваши loadData здесь не нужны, я также предлагаю это вид API, его не тестировали, рассматривают его как псевдо-реализацию

waterfall([this.loadFromLS(this.loadFromApi), this.setData]

Таким образом, водопад принимает массив обещаний, которые вызываются друг за другом, и прохождение передает результат до следующего обещания

См. полное псевдо-демо ниже


    function waterfall (...args) {
        const promises = [].concat(...args)
        const first = promises[0]

        let firstPromise = first()
        for (let i = 1; i < promises.length; i++) {
            firstPromise  = firstPromise.then(() => promises[i])
        }
        return firstPromise
    }

    class MyClass {
        loadData(): Promise<any> {
            const data = waterfall([this.loadFromLS(this.loadFromApi), this.setData])
            return Promise.resolve(data)
        }

        loadFromLocalStorage(replacement) {
            return function (): Promise<void> {
                if (!this.isSupported(() => localStorage) {
                    return replacement && replacement()
                }
                const data = JSON.parse(localStorage.getItem('metadata'))
                return Promise.resolve(data)
            }
        }

       loadFromApi() : Promise<void> {
           return this.http.get<{date?:string,ddi?:number,stoff?:number}>(this.apiUrl+'meta.json')
       }

        setData(data) : Promise<void> {
            let promises: Promise<Pilot|void>[] = [];
            if (data.ddi) {
                promises.push( this.pilotService.getById(data.ddi).then(ddi => this.ddi = ddi) );
            }
             // There's more promises by the way, this is simplified
            return Promise.all(promises)
        }
    }

0 голосов
/ 16 января 2020

Было сделано три изменения, чтобы исправить это:

  • resolve() / reject() было заменено на resolve(true) / resolve(false)
  • return после resolve / reject для остановки дальнейшего исполнения
  • .then() / .catch были заменены на async / await

Вот мой окончательный код:

loadData(): Promise<void> {
    return new Promise(async (resolve, reject) => {
        if( await this.loadFromLocalStorage() ) {
            resolve();
            return
        }
        await this.loadFromApi();
        resolve()
    });
}

loadFromLocalStorage() : Promise<boolean> {
    return new Promise(async (resolve, reject) => {
        if( !this.isSupported(() => localStorage) ) {resolve(false); return;}
        data = JSON.parse( localStorage.getItem('metadata') );
        if( !data || !data.date ) {console.log("no data"); resolve(false); return;}
        await this.setData(data, false);
        resolve(true);
    });
}

loadFromApi() : Promise<boolean> {
    return new Promise(async (resolve, reject) => {
        this.http.get<{date?:string,ddi?:number,stoff?:number}>(this.apiUrl+'meta.json')
            .subscribe( async data => {
                await this.setData(data, true)
                resolve(true);
            },
            () => {
                resolve(false);
            });
    });
}

setData(data, saveToLocal: boolean) : Promise<null> {
    return new Promise((resolve) => {
        let promises: Promise<Pilot|void>[] = [];
        if (data.ddi) {
            promises.push( this.pilotService.getById(data.ddi).then(ddi => this.ddi = ddi) );
        }
        // There's more promises by the way, this is simplified
        Promise.all(promises).then(function() {
            resolve();
        });
    });
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...