如何在嵌套的Promise中传播resolve()?

时间:2019-11-17 17:55:37

标签: javascript reactjs promise

我正在编写一个React应用程序,在某些情况下,我必须解决嵌套的Promise。代码可以正常工作,但是我无法将resolve()函数传播到最外层,因此无法获得返回值。

这是代码:

  writeData(data) {
    this.store.dispatch({type: "START_LOADER"})

    return new Promise((resolve, reject) => {
      this.manager.isDeviceConnected(this.deviceId).then(res => {
        this.manager.startDeviceScan(null, null, (error, device) => {
          if (device.id === this.deviceId) {

            resolve("test") // -> this is propagate correctly

            device.connect().then((device) => {
              this.store.dispatch({type: "SET_STATUS", payload: "Device is connected!\n"})
              return device.discoverAllServicesAndCharacteristics()
            }).then((device) => {
              device.writeCharacteristicWithoutResponseForService(
                data.serviceId,
                data.charId,
                data.dataToWrite
              ).then(res => {

                resolve("test2") // -> this is not propagated

              }).catch(error => {
                reject(error.message)
              })
            }).catch((error) => {
              reject(error.message)
            });
          }
        });
      }).catch(error => {
        reject(error.message)
      })
    })
  }
...
...

  async writeAsyncData(data) {
    await this.writeData(data)
  }

当我调用此函数时:

      this.writeAsyncData({data}).then(response => {
        // here I expect whatever parameter I have passed to resolve()
        console.log(response)
      })

万一我没有评论resolve("test"),我可以console.log没问题,但是如果我发表评论,resolve("test2")不会显示在console.log和{{ 1}}未定义。

如何确保甚至内部response的嵌套参数也可以到达resolve

1 个答案:

答案 0 :(得分:5)

要正确嵌套承诺,请勿将它们包装在另一个手动创建的承诺中。那是一种反模式。取而代之的是,您返回内部承诺,然后将它们链接起来。最内层的承诺回报将是整个链条的解决价值。

此外,当您有任何返回回调的异步操作时,必须对它们进行承诺,以使您可以对所有带有承诺的异步控制流进行处理,并且还可以始终如一地进行正确的错误处理。不要将简单的回调与promise混在一起。控制流程,尤其是正确的错误处理变得非常非常困难。您以诺言开始,使所有异步操作都使用诺言。

虽然此代码可能是async/await最简单的代码,但我将首先向您展示如何通过返回每个内部内在保证来正确地链接所有嵌套的内在保证。

并且,为了简化嵌套代码,可以将其展平,这样,您无需将承诺的每个级别都进行更深入的缩进,只需将承诺返回到顶层并继续在那里进行处理即可。

总结这些建议:

1。不要将现有的承诺包装在另一个手动创建的承诺中。这是一个承诺反模式。除了不必要之外,通过适当的错误处理和错误传播也很容易犯错误。

2。保证任何简单的回调。这使您可以使用promises进行所有控制流,这使得避免错误或不知道如何正确传播错误的棘手情况变得容易得多。

3。从.then()处理程序中返回所有内部的Promise,以将它们正确地链接在一起。。这允许最里面的返回值成为整个Promise链的已解析值。它还允许错误在整个链中正确传播。

4。压扁链。。如果您将多个promise链在一起,请将它们压扁,这样您就总是返回到顶层,而不会产生越来越深的嵌套。您必须使事情更深入的一种情况是,您的诺言链中有条件(您在这里没有条件)。

在您的代码中应用了以下建议:

// Note: I added a timeout here so it will reject
// if this.deviceId is never found
// to avoid a situation where this promise would never resolve or reject
// This would be better if startDeviceScan() could communicate back when
// it is done with the scan
findDevice(timeout = 5000) {
    return new Promise((resolve, reject) => { 
        const timer = setTimeout(() => {
             reject(new Error("findDevice hit timeout before finding match device.id"));
        }, timeout);
        this.manager.startDeviceScan(null, null, (error, device) => { 
            if (error) {
                reject(error); 
                clearTimeout(timer);
                return
            }
            if (device.id === this.deviceId) {
                resolve(device); 
                clearTimeout(timer);
            }
        });
    });
}

writeData(data) {
    this.store.dispatch({type: "START_LOADER"});
    return this.manager.isDeviceConnected(this.deviceId).then(res => {
        return this.findDevice();
    }).then(device => {
        return device.connect();
    }).then(device => {
        this.store.dispatch({type: "SET_STATUS", payload: "Device is connected!\n"})
        return device.discoverAllServicesAndCharacteristics();
    }).then(device => {
        return device.writeCharacteristicWithoutResponseForService(
            data.serviceId,
            data.charId,
            data.dataToWrite
        );
    }).then(res => {
        return "test2";  // this will be propagated
    });
}

以下是使用async / await的版本:

findDevice(timeout = 5000) {
    return new Promise((resolve, reject) => { 
        const timer = setTimeout(() => {
             reject(new Error("findDevice hit timeout before finding match device.id"));
        }, timeout);
        this.manager.startDeviceScan(null, null, (error, device) => { 
            if (error) {
                reject(error); 
                clearTimeout(timer);
                return
            }
            if (device.id === this.deviceId) {
                resolve(device); 
                clearTimeout(timer);
            }
        });
    });
}

async writeData(data) {        
    this.store.dispatch({type: "START_LOADER"});
    let res = await this.manager.isDeviceConnected(this.deviceId);
    let deviceA = await this.findDevice();
    let device = await deviceA.connect();
    this.store.dispatch({type: "SET_STATUS", payload: "Device is connected!\n"})
    await device.discoverAllServicesAndCharacteristics();
    let res = await device.writeCharacteristicWithoutResponseForService(
          data.serviceId,
          data.charId,
          data.dataToWrite
    );
    return "something";    // final resolved value 
}

注意:在您的原始代码中,您有两个device的替代定义。我在第一版代码中将其保留在那里,但在第二版中将第一个更改为deviceA

注意:在编写代码时,如果this.manager.startDeviceScan()从未在device.id === this.deviceId的位置找到匹配的设备,则代码将被卡住,从不解析或拒绝。这似乎很难找到等待发生的错误。在绝对最坏的情况下,它应该有一个超时,如果从未找到,则将拒绝该超时,但是startDeviceScan的实现可能需要在扫描完成后进行通讯,以便外部代码可以在找不到匹配设备的情况下拒绝。 / p>

注意:我看到您从不使用this.manager.isDeviceConnected(this.deviceId);中的解析值。这是为什么?如果设备未连接,是否拒绝?如果没有,这似乎是无操作的操作(没有任何用处)。

注意:您致电并等待device.discoverAllServicesAndCharacteristics();,但从不使用任何结果。这是为什么?