简体   繁体   English

强制异步调用行为同步

[英]Force an Asynchronous call to behave Synchronously

In my React app, I'm trying to calculate a value based on three other values. 在我的React应用程序中,我试图基于其他三个值来计算一个值。 I've contained all of the calculation logic to the back end, which is a microservice I make asynchronous calls to. 我将所有计算逻辑都包含在后端,这是我进行异步调用的微服务。 The function in which I am asynchronously trying to get that calculated value is in the middle of many synchronous hooks. 我异步尝试获取该计算值的函数位于许多同步挂钩中。

In the UI layer, I call the function which I want to return the end result (returned asynchronously). 在UI层中,我调用要返回最终结果(异步返回)的函数。 That called function calls another function, which calls another function, which returns a new Promise. 被调用的函数调用另一个函数,后者调用另一个函数,该函数返回一个新的Promise。 See code below: 参见下面的代码:

// DateUI.js (layer 1)
selectDate(dateField, flight, idx, saved, momentTime, e) {
    if (moment(momentTime).isValid()) {
        if (dateField == "StartDate") {
            // The initial problematic function call, need to set endDate before I continue on
            let endDate = PlanLineActions.calculateFlightEndDate(periodTypeId, numberOfPeriods, momentTimeUnix);

            flight.set("EndDate", endDate);
        }

        this.theNextSyncFunction(..., ..., ...);
    }
}


// DateActions.js (layer 2)
calculateFlightEndDate(periodTypeId, numberOfPeriods, startDate) {
    let plan = new Plan();

    plan.getFlightEndDate(periodTypeId, numberOfPeriods, startDate).then(function(response) {
        // response is JSON: {EndDate: "12/05/2016"}
        response.EndDate;
    }, function(error) {
        log.debug("There was an error calculating the End Date.");
    });
}


// DateClass.js (layer 3)
getFlightEndDate(periodTypeId, numberOfPeriods, startDate) {
    let path = '/path/to/microservice';
    return this.callServer(path, 'GET', {periodTypeId: periodTypeId, numberOfPeriods: numberOfPeriods, startDate: startDate});
}


// ServerLayer.js (layer 4)
callServer(path, method = "GET", query = {}, data, inject) {
    return new Promise((resolve, reject) => {
        super.callServer(uri.toString(),method,data,inject).then((data) => {
            resolve(data);
        }).catch((data) => {
            if (data.status === 401) {
                AppActions.doRefresh();
            }
            reject(data);
        });
    });
}

I am under the impression that, because ServerLayer.js (layer 4) returns a new Promise (and thus DateClass.js (layer 3)), calling plan.getFlightEndDate(...).then(function(response) {... will not complete until the response comes back resolved or rejected. This is not currently happening, as the code in DateUI.js (layer 1) will continue on to call this.theNextSyncFunction , and then resolve ~50ms later with the proper data. 我的印象是,由于ServerLayer.js(第4层)返回了一个new Promise (因此又返回了DateClass.js(第3层)),因此调用plan.getFlightEndDate(...).then(function(response) {...直到响应返回被解析或拒绝后, plan.getFlightEndDate(...).then(function(response) {...才会完成。这目前尚未发生,因为DateUI.js(第1层)中的代码将继续调用this.theNextSyncFunction ,然后在约50ms后使用正确的数据进行解析。

How do I force PlanLineActions.calculateFlightEndDate(...) in DateUI.js (layer 1) to complete with a response before I continue on with selectDate()? 在继续执行selectDate()之前,如何强制DateUI.js(第1层)中的PlanLineActions.calculateFlightEndDate(...)完成响应?

I think you don't understand how promises work. 我认为您不了解诺言如何运作。

First of all, functions always return immediately so you won't ever block execution of the next lines of code ( flight.set and theNextSyncFunction() in your case). 首先,函数总是立即返回,因此您将永远不会阻止下一行代码的执行theNextSyncFunction()在您的情况下为flight.settheNextSyncFunction() )。 This is the point of returning a promise: you get a promise immediately that you can attach a callback to (using then() ) that will get invoked later . 这是返回诺言的要点:您立即得到诺言,您可以将回调附加到(使用then() ),该回调稍后将被调用。 If you want code to wait for the promise to resolve you have to put it in a then() callback. 如果您希望代码等待承诺解决, 则必须将其放入then()回调中。

Secondly, your calculateFlightEndDate() is not returning anything at all so endDate = calculateFlightEndDate() is simply setting endDate to undefined . 其次,您的calculateFlightEndDate()根本不返回任何内容,因此endDate = calculateFlightEndDate()只是将endDate设置为undefined

Solution

You should return a promise from calculateFlightEndDate() and put the code you want to execute afterwards inside a then() callback: 您应该从calculateFlightEndDate()返回一个promise,然后将要执行的代码放在then()回调中:

calculateFlightEndDate(periodTypeId, numberOfPeriods, startDate) {
    let plan = new Plan();

    return plan.getFlightEndDate(periodTypeId, numberOfPeriods, startDate).then((response) => {
        // response is JSON: {EndDate: "12/05/2016"}
        return response.EndDate;
    }, (error) => {
        log.debug("There was an error calculating the End Date.");
    });
}

if (moment(momentTime).isValid()) {
    if (dateField == "StartDate") {
        PlanLineActions.calculateFlightEndDate(periodTypeId, numberOfPeriods, momentTimeUnix).then((endDate) => {
            flight.set("EndDate", endDate);
            this.theNextSyncFunction(...);
        });
    }
}

You could also look into using ES7 async and await which allows you to write your async code so that it looks synchronous, but uses promises under the hood to accomplish the same thing. 您还可以考虑使用ES7 async and await ,它允许您编写异步代码,使其看起来是同步的,但在幕后使用promise来完成同样的事情。

You can't force something to be synchronous if it goes outside of the event loop like an ajax call. 如果某事物超出了事件循环(如ajax调用)的范围,则不能强制使其同步。 You're going to need something that looks like this: 您将需要如下所示的内容:

PlanLineActions.calculateFlightEndDate(periodTypeId, numberOfPeriods, momentTimeUnix)
  .then(endDate => {
    this.theNextSyncFunction(..., ..., ...);
  })

In order to do this, calculateFlightEndDate will also need to return a promise, and thus it's a good thing that promises are chainable. 为了做到这一点, calculateFlightEndDate也将需要返回一个promise,因此promise可链接是一件好事。

calculateFlightEndDate(periodTypeId, numberOfPeriods, startDate) {
  let plan = new Plan();

  // return promise!
  return plan.getFlightEndDate(periodTypeId, numberOfPeriods, startDate).then(response => {
    return response.EndDate; // must return here
  }, error => {
    log.debug("There was an error calculating the End Date.");
  });
}

That should do it.. and one more thing: you're doubling up on promises in your server call. 那应该做到的……还有另一件事:您在服务器调用中的承诺增加了一倍。 If something has .then it's already a promise, so you can just return that directly. 如果某物具有.then已经是一个承诺,那么您可以直接将其返回。 no need to wrap in new Promise (a promise in a promise.. no need!) 无需包装new Promise (一个new Promise中的一个new Promise ..不需要!)

callServer(path, method = "GET", query = {}, data, inject) {
  // just return!
  return super.callServer(uri.toString(),method,data,inject).then((data) => {
    return data;
  }).catch((data) => {
    if (data.status === 401) {
      AppActions.doRefresh();
    }
    throw data; // throw instead of reject
  });
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM