繁体   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