簡體   English   中英

如何使用/反映嵌套的承諾?

[英]How to use/refector nested promises?

我在我的 TypeScript 應用程序中使用 Promises 從最好的來源獲取我的數據。 首先應該嘗試 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 獲取)失敗時不會發生某些操作的解決方案。

讓我們繪制你的 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)
        }
    }

您發布的答案中的改進代碼很好,但仍然可以使其更加簡潔和可維護:

  1. 請注意,異步函數始終返回 Promise。 您基本上只是將每個函數體包裝在一個 Promise 中以使它們返回一個,但是如果您使包含的函數異步,則這是不必要的。
  2. 我沒有使用布爾值來指示成功/失敗狀態,而是使用了拒絕的承諾,因為它在 JavaScript 中更慣用,並且使用 async/await,您可以以直觀的“同步”方式編寫它( throw異步函數會導致返回的 Promise 拒絕)
  3. 當您可以返回一個表示成功的值時,我強烈建議您這樣做——您是否實際使用它並不重要,但如果您確實需要它,它可以簡化現有代碼。
  4. 當使用await作為處理await ed Promise 拒絕的方法時,您可以使用try/catch ,它使代碼非常直觀
  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);
}

此代碼遵循 Promises 和 async/await 的更慣用用法。 每個函數要么在成功時以有意義的值解析,要么在因任何原因失敗時以有意義的錯誤拒絕

由於 async/await,任何錯誤也會導致拒絕“冒泡”堆棧一直到調用者,即如果loadFromApi()出現問題,Promise 將拒絕並返回錯誤,然后導致loadData()也拒絕相同的錯誤,將其暴露給調用loadData()的函數,並允許您封裝此數據 API 的錯誤處理(由任何內部函數引起的任何錯誤都可以“冒泡”到入口點loadData()並隨心所欲處理)

為了解決這個問題,進行了三處更改:

  • resolve() / reject()resolve(true) / resolve(false)取代
  • resolve / reject后調用return以停止進一步執行
  • .catch .then() / .catchasync / 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();
        });
    });
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM