首页 > 解决方案 > 不管结果如何,在承诺之后执行次要行动?

问题描述

我发现了这个先前的线程(如何执行相同的操作,无论承诺是否履行?),但它已经 5 岁了,并且引用 winjs 是一个杂项。

我想做的是加载数据元素列表。我有列表的本地副本和元素的本地副本——但它们可能在服务器端发生了变化。

该过程应该像这样工作:将 LIST 从数据库加载到本地存储(与本地比较)-> 然后从 LIST 中列出的数据库加载(多个)数据元素。

所以如果“loadList”异步函数成功......我想运行“loadElements”异步函数。如果 loadList 函数拒绝......我仍然想运行“loadElements”函数(它会触发多个获取请求 - 每个元素一个)。

“使用'finally'”我听到你说......但我想将“loadList”解析/拒绝和“loadElements”解析/拒绝函数的结果传递给调用函数。据我所知,“终于”没有接收或传递属性。

我想将结果传递给调用函数的原因是查看拒绝原因是否是可接受的原因,我是否可以信任本地副本作为权威副本(例如,如果数据库不包含 LIST,我可以相信本地列表是权威版本)......所以我需要一种方法来分析调用函数中的“失败”。

这是我所拥有的:

export function syncLinkTablesAndElementsWithDB(username) { 
return (dispatch, getState) => {
        return new Promise((resolve, reject) => {
            dispatch(loadLinkTableAndElementsFromDB(STATIONS_LINK_TABLE_TYPE, username))
                .then((msg) => {
                    console.log("loadLinkTableAndElementsFromDB RESOLVED: ", msg);
                    resolve(msg)

                })
                .then(() => {
                    dispatch(pushLinkTableToDB(STATIONS_LINK_TABLE_TYPE, username))
                    dispatch(pushAllUserStationsToDB(username))
                 })
                .catch((allPromReasons) => {
                    console.log("loadLinkTableAndElementsFromDB REJECTED: ", allPromReasons);
                    allReasonsAcceptable = true;
                    allPromReasons.forEach(reason => {
                        if (!isAcceptableLoadFailureReasonToOverwrite(reason)) {
                            allReasonsAcceptable = false;
                        }
                    });
                    if (allReasonsAcceptable) {
                        //TODO:   DO push of local to DB
                        // eventually return results of the push to DB...

                    } else {
                        reject(allPromReasons)
                    }
                })
        });
    }
}


export function loadLinkTableAndElementsFromDB(tableType, username) {
    return (dispatch, getState) => {
        return new Promise((resolve, reject) => {
            dispatch(loadLinkTableFromDB(tableType, username))
                .then(successMsg => {
                    resolve(Promise.all([successMsg, dispatch(loadAllUsersStationsFromDB(username)).catch(err=>err)]))
                })
                .catch(err => {
                    reject(Promise.all([err, dispatch(loadAllUsersStationsFromDB(username)).catch(err=>err)]))
                })
        });
    }
}

export function loadAllUsersStationsFromDB(username) {
    return (dispatch, getState) => {
        return new Promise((resolve, reject) => {
            let linkTable = getStationsLinkTable(username); // get the local link table
            if (linkTable && Array.isArray(linkTable.stations)) { // if there is a local station list
                let loadPromises = linkTable.stations.map(stationID => dispatch(loadStationFromDB(stationID)).catch((err) => err));
                Promise.all(loadPromises)
                    .then((allReasons) => {
                        let allSuccesses = true;
                        allReasons.forEach(reason => {
                            if (!reason.startsWith(SUCCESS_RESPONSE)) {
                                allSuccesses = false;
                            }
                        });
                        if (allSuccesses) {
                            resolve(SUCCESS_RESPONSE + ": " + username);
                        } else {
                            reject(allReasons);
                        }
                    })
            } else {
                return reject(NO_LINK_TABLE_AVAILABLE + ": " + username);
            }
        });
    };
}

loadStationFromDB 和 loadLinkTableFromDB 做你所期望的......尝试从数据库中加载这些东西。如果您认为值得,我可以包含他们的代码。

------------ 编辑 ------------ 澄清我要完成的工作:

我正在尝试将本地存储与数据库同步。我想通过从数据库中提取数据,比较时间/日期戳来做到这一点。这将使本地存储版本成为所有数据的权威副本。从数据库加载后,我想将本地存储版本推送到数据库。

我需要注意这样一个事实,即数据库通常根本没有数据,因此可能会在拉取时“拒绝”......即使在同步的情况下,拒绝是可以接受的并且不应该停止同步过程。

根据下面的建议,我修改了我的代码:

export function loadLinkTableAndElementsFromDB(tableType, username) {
    console.log("loadLinkTableAndElementsFromDB(", tableType, username, ")");
    return (dispatch, getState) => {
        return new Promise((resolve, reject) => {
            dispatch(loadLinkTableFromDB(tableType, username))
                .then(successMsg => {
                    console.log("loadLinkTableFromDB RESOLVED: ", successMsg)
                    resolve(Promise.all([successMsg, dispatch(loadAllUsersStationsFromDB(username)).catch(err => err)]))
                })
                .catch(err => {
                    console.log("loadLinkTableFromDB REJECTED: ", err)
                    reject(Promise.all([err, dispatch(loadAllUsersStationsFromDB(username)).catch(err => err)]))
                })
        });
    }
}

export function syncLinkTablesAndElementsWithDB(username) {
    console.log("syncLinkTablesAndElementsWithDB(", username, ")");
    return (dispatch, getState) => {
        dispatch(loadLinkTableFromDB(STATIONS_LINK_TABLE_TYPE, username))
            .then((successLoadLinkTableMsg) => {
                console.log('Successfully loaded link table: ', successLoadLinkTableMsg)
                return dispatch(pushLinkTableToDB(STATIONS_LINK_TABLE_TYPE, username))
            })
            .catch((rejectLoadLinkTableReason) => {
                console.log("Failed to load link table from DB: " + rejectLoadLinkTableReason);
                if (allReasonsAcceptableForOverwrite(rejectLoadLinkTableReason)) {  // some rejection reasons are accectable... so if failed reason is okay.... 
                    console.log("Failure to load link table reasons were acceptable... pushing local link table anyway");
                    return dispatch(pushLinkTableToDB(STATIONS_LINK_TABLE_TYPE, username))
                } else {
                    console.log("Throwing: ", rejectLoadLinkTableReason);
                    throw rejectLoadLinkTableReason;
                }
            })  
            .then((successPushLinkTaleMsg) => { 
                console.log("Successfully pushed link table: " + successPushLinkTaleMsg);
                return dispatch(loadAllUsersStationsFromDB(username)); // I want this to occur regardless of if the link table stuff succeeds or fails...  but it must occur AFTER the loadLinkTableFromDB at least tries...
            })
            .catch((rejectPushLinkTableReason) => {
                console.log("Failed to push link table: " + rejectPushLinkTableReason);
                return dispatch(loadAllUsersStationsFromDB(username)); // I want this to occur regardless of if the link table stuff succeeds or fails...  but it must occur AFTER the loadLinkTableFromDB at least tries... 
            })
            .then((successLoadAllUserStationsMsg) => {
                console.log("Successfully loaded all user stations: " + successLoadAllUserStationsMsg);
                return dispatch(pushAllUserStationsToDB(username))
            })
            .catch((rejectLoadAllUserStationsReason) => {
                console.log("Failed to push all users stations: " + rejectLoadAllUserStationsReason);
                if (allReasonsAcceptableForOverwrite(rejectLoadAllUserStationsReason)) {  // some rejection reasons are accectable... so if failed reason is okay.... 
                    console.log("Load users stations reasons are acceptable...");
                    return dispatch(pushAllUserStationsToDB(username))
                } else {
                    console.log("throwing: ", rejectLoadAllUserStationsReason);
                    throw rejectLoadAllUserStationsReason;
                }
            })
            .then((successPushAllUserStationsMgs) => {
                console.log("Successfully pushed all users stations: " + successPushAllUserStationsMgs);
                return Promise.resolve();
            })
            .catch((rejectPushAllUserStationsReason) => {
                console.log("Failed to push all users stations: " + rejectPushAllUserStationsReason);
                throw rejectPushAllUserStationsReason;
            })
    };
}


export function syncAllWithDB(username) { 
    return (dispatch, getState) => {

        // other stuff will occur here...

            dispatch(syncLinkTablesAndElementsWithDB(username))  // *** Error here ***
                .then((successMsg) => {
                    console.log("Successful sync for : " + successMsg);
                })
                .catch(allReasons => {
                    console.warn("Link tables and elements sync error: ", allReasons);
                })
        // });
    }
}

不幸的是,我现在在 syncAllWithDB 函数的调度中得到“TypeError:dispatch(...) is undefined”。这个功能没变...

标签: javascriptreduxpromiseprogressive-web-appsthunk

解决方案


我并不完全遵循你想要完成的事情(更多内容见下文),但这里要做的第一件事是清理流程,而不是new Promise()围绕现有的承诺进行额外的包装。永远没有理由这样做:

 function someFunc() {
      return new Promise((resolve, reject) => {
           callSomething.then(result => {
               ...
               doSomethingElse(result).then(result2 => {
                    ...
                    resolve(result2);
               }).catch(err => {
                    ...
                    reject(err);
               });
           }).catch(err => {
               ...
               reject(err);
           });
      });
 }

这是一个众所周知的 promise 反模式。您不需要将额外的手动创建的 Promise 包裹在已经做出承诺的函数周围。相反,你可以只返回你已经拥有的承诺。这称为“承诺链”。从链中,您可以从任何地方拒绝或解决链。

 function someFunc() {
     return callSomething.then(result => {
         ...
         // return promise here, chaining this new async operation 
         // to the previous promise
         return doSomethingElse(result).then(result2 => {
              ...
              return result2;
         }).catch(err => {
              ...
              // after doing some processing on the error, rethrow
              // to keep the promise chain rejected
              throw err;
         });
    }).catch(err => {
         ...
         reject err;
    });
 }

或者,您甚至可以像这样扁平化承诺链:

 function someFunc() {
     return callSomething.then(result => {
         ...
         return doSomethingElse(result);
     }).then(result2 => {
         ...
         return result2;
    }).catch(err => {
         ...
         throw err;
    });
 }

作为一个例子,你可以syncLinkTablesAndElementsWithDB()像这样简化:

export function syncLinkTablesAndElementsWithDB(username) { 
    return (dispatch, getState) => {
        return dispatch(loadLinkTableAndElementsFromDB(STATIONS_LINK_TABLE_TYPE, username)).then((msg) => {
            console.log("loadLinkTableAndElementsFromDB RESOLVED: ", msg);
            dispatch(pushLinkTableToDB(STATIONS_LINK_TABLE_TYPE, username))
            dispatch(pushAllUserStationsToDB(username))
            // have msg be the resolved value of the promise chain
            return(msg);
        }).catch((allPromReasons) => {
            console.log("loadLinkTableAndElementsFromDB REJECTED: ", allPromReasons);
            let allReasonsAcceptable = allPromReasons.every(reason => {
                return isAcceptableLoadFailureReasonToOverwrite(reason);
            });
            if (allReasonsAcceptable) {
                //TODO:   DO push of local to DB
                // eventually return results of the push to DB...
            } else {
                // have promise stay rejected
                throw allPromReasons;
            }
        });
    }
}

至于您的其余问题,您是在问这个:

所以如果“loadList”异步函数成功......我想运行“loadElements”异步函数。如果 loadList 函数拒绝......我仍然想运行“loadElements”函数(它会触发多个获取请求 - 每个元素一个)。

但是,你的代码中没有调用函数,loadList()所以loadElements()你把我弄丢了,所以我不确定如何提出具体的代码建议。

.then()Promise 链中的处理程序中,您可以做三件事:

  1. 返回一个值。 该值成为承诺链的已解决值。
  2. 返回一个承诺。 该承诺附加到承诺链,并且当您返回的承诺解决/拒绝(或任何也链接到它的任何东西都解决时,整个承诺链(调用者将观察的最高承诺)最终将解决/拒绝/拒绝)。
  3. 抛出异常。 所有.then()处理程序都会自动监视异常,如果抛出任何异常,则承诺链将自动拒绝,并将异常值设置为拒绝原因。

因此,这为您提供了最终的灵活性,可以用值或错误完成 Promise 链,或者将其链接到另一个 Promise(更多异步操作)。


推荐阅读