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

ste*_*ste 0 javascript promise reactjs

我正在编写一个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)
  }

Run Code Online (Sandbox Code Playgroud)

当我调用此函数时:

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

万一我不加resolve("test")注释,我可以console.log没有问题,但是如果我发表评论,resolve("test2")它不会显示console.log并且response是未定义的。

我如何才能确保甚至内部的嵌套参数都resolve可以到达console.log

jfr*_*d00 5

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

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

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

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

总结这些建议:

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

2.承诺任何普通的回调。 这样一来,您就可以使用Promise进行所有控制流,从而可以更轻松地避免错误或棘手的情况,因为您不知道如何正确传播错误。

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

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
    });
}
Run Code Online (Sandbox Code Playgroud)

这是使用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 
}
Run Code Online (Sandbox Code Playgroud)

注意:在原始代码中,您有的两个覆盖定义device。我在第一个版本的代码中保留了该代码,但将第一个更改deviceA为第二个代码。

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

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

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