在 promise.then 或 promise.catch 中调用时,Array.push 的工作很奇怪

Posted

技术标签:

【中文标题】在 promise.then 或 promise.catch 中调用时,Array.push 的工作很奇怪【英文标题】:Array.push works strange when called in promise.then or promise.catch 【发布时间】:2021-06-16 18:27:12 【问题描述】:

我编写了一个方法,该方法应返回连接到客户端拥有的网络的设备列表。

export const connectedDevicesCore = (***Id: string, ***AuthToken: string) =>
    Service.list***ConnectionsCore(***Id, ***AuthToken).then(***ConnectedDevices => 
        let listPromises: Promise<any>[] = [];
        let listDevices: IDeviceData[] = [];
        ***ConnectedDevices.data.forEach((member: IZeroTierMember) => 
            listPromises.push(apiUtils.ApiGet(`http://$member.config.ipAssignments[0]:5000/api/Main`).then((response: any) => 
                let device: IDeviceData = response.data;
                device.IP*** = member.config.ipAssignments[0];
                listDevices.push(device);
            ).catch(() => 
                let device: IDeviceData = IDeviceExample;
                device.IP*** = member.config.ipAssignments[0];
                device.DeviceName = member.name;
                device.Status = 'Have no client';
                listDevices.push(device);
            ));
        );
        return Promise.allSettled(listPromises).then(() => listDevices);
    );

ApiGet 方法:

export const ApiGet = (apiString: string, authToken: string | null = null) => 
    let config =  headers:  Authorization: `Bearer $authToken`  ;
    let promise = axios.get(apiString, authToken != null ? config : undefined);
    return promise
;

这个想法是,如果客户端响应了请求,那么他传输的数据应该添加到所有设备的数组中,如果请求的客户端没有响应(即不存在)添加一个相同的设备没有客户的IP和信息。

问题是向数组写入数据很奇怪,并非总是如此。

示例 1:网络上有 3 台设备,每台都没有客户端。

    当第一个请求返回“ETIMEDOUT”错误时,listDevices 如下所示:
[  DeviceName: 'HP Server',
    ID: 0,
    IP: '',
    IP***: '192.168.193.227',
    MAC_Adress: '',
    Status: 'Have no client',
    AudioOutputs: []  ]
    当第二个请求返回“ETIMEDOUT”错误时,listDevices 如下所示:
[  DeviceName: 'Huawei P Smart 2021',
    ID: 0,
    IP: '',
    IP***: '192.168.193.64',
    MAC_Adress: '',
    Status: 'Have no client',
    AudioOutputs: [] ,
   DeviceName: 'Huawei P Smart 2021',
    ID: 0,
    IP: '',
    IP***: '192.168.193.64',
    MAC_Adress: '',
    Status: 'Have no client',
    AudioOutputs: []  ]
    当第三个请求返回“ETIMEDOUT”错误时,listDevices 如下所示:
[  DeviceName: 'Test Client',
    ID: 0,
    IP: '',
    IP***: '192.168.193.147',
    MAC_Adress: '',
    Status: 'Have no client',
    AudioOutputs: [] ,
   DeviceName: 'Test Client',
    ID: 0,
    IP: '',
    IP***: '192.168.193.147',
    MAC_Adress: '',
    Status: 'Have no client',
    AudioOutputs: [] ,
   DeviceName: 'Test Client',
    ID: 0,
    IP: '',
    IP***: '192.168.193.147',
    MAC_Adress: '',
    Status: 'Have no client',
    AudioOutputs: []  ]

示例2:网络上有3台设备,只有第一台设备有客户端。

    第一个请求将客户端返回的数据写入listDevices。 第二个请求的行为与示例 1 相同,只是它不会覆盖第一个客户端的响应。 第三个请求的行为与示例 1 相同,只是它不会覆盖第一个客户端的响应。

即例如2,最终的答案会是这样的:

[  DeviceName: 'Test Client',
    ID: 0,
    IP: '192.168.8.3',
    IP***: '192.168.193.147',
    MAC_Adress: 'C0B883046C3F',
    Status: 'Succes',
    AudioOutputs: [...sth: "sth"...] ,
   DeviceName: 'Huawei P Smart 2021',
    ID: 0,
    IP: '',
    IP***: '192.168.193.64',
    MAC_Adress: '',
    Status: 'Have no client',
    AudioOutputs: [] ,
   DeviceName: 'Huawei P Smart 2021',
    ID: 0,
    IP: '',
    IP***: '192.168.193.64',
    MAC_Adress: '',
    Status: 'Have no client',
    AudioOutputs: []  ]

我尝试了这段代码的许多变体,以保持合乎逻辑的想法,但是很遗憾,它没有任何帮助。

此代码在 Node.js 10.23.0 + Typescript 4.2.3 上运行

任何想法我做错了什么?

【问题讨论】:

“第二个请求的行为与示例 1 相同,只是它不会覆盖第一个客户端的响应。” 你能解释一下吗?我没有看到任何代码会使其覆盖第一个客户端的响应,因此它不这样做是有道理的。但也许我误会了? @T.J.Crowder 问题是这段代码不存在,但它发生了。 谢谢,这证实了我在代码中找到的内容。我会发布它。 【参考方案1】:

我认为问题在于,在失败的情况下,您并没有复制 IDeviceExample(它的名字很奇怪——我希望它是一个接口,但您使用它就像一个目的)。在您的 catch 处理程序中,更改:

let device: IDeviceData = IDeviceExample;

let device: IDeviceData = ...IDeviceExample;

这样您就可以创建一个新对象,并在该对象上分配给device.IP*** 不会影响您正在使用它的其他地方。 (或者如果需要深拷贝,see here。问题中的代码不需要深拷贝,但我不知道你在其他代码中如何处理这些设备对象。)


旁注:我建议不要在承诺解决时推送到列表并使用allSettled,而是建议内联处理拒绝(就像你一样)并使用Promise.all而不使用额外的数组(Promise.all不会看到任何拒绝,因为您正在内联处理它们)。另外,如果response.dataIDeviceData,我建议使用data: IDeviceData 作为response 而不是any 的类型(通常,避免使用any)。实际上,如果apiUtils.ApiGet 具有泛型类型参数,则不需要显式指定类型。这是一个示例(还包括根据您在下面的评论将名称 IDeviceExample 更改为 DeviceExample):

export const connectedDevicesCore = (***Id: string, ***AuthToken: string) =>
    Service.list***ConnectionsCore(***Id, ***AuthToken).then(***ConnectedDevices => 
        const listPromises = Promise.all(***ConnectedDevices.data.map(member => 
        //        ^ TypeScript can infer the type of this rather than your having to specify it explicitly (and no need for `any`)
            return apiUtils.ApiGet<IDeviceData>(`http://$member.config.ipAssignments[0]:5000/api/Main`)
        //                        ^^^^^^^^^^^^^−−−− *** type argument saying what the response will be
                .then((data: device) =>  // *** No need for `any`, and you can use destructuring to put `data` in `device`
                    device.IP*** = member.config.ipAssignments[0];
                    return device;
                )
                .catch(() => 
                    let device: IDeviceData = ...DeviceExample; // *** Copy the example object
                    device.IP*** = member.config.ipAssignments[0];
                    device.DeviceName = member.name;
                    device.Status = "Have no client";
                    return device; // *** Converts rejection to fulfillment with this value
                );
        ));
        return listPromises;
    );

只是 FWIW。 :-) Playground link

【讨论】:

谢谢,改成let device: IDeviceData = ... IDeviceExample; 成功了!顺便说一句,IDeviceExample 只是一个重复界面中所有字段的对象。这样做是为了可以创建类。我将把这个 IDeviceExample 重命名为 DeviceExample。 @Crossmax - 很高兴有帮助! FWIW,我最后更新了我的示例以摆脱 any 类型等。编码愉快!

以上是关于在 promise.then 或 promise.catch 中调用时,Array.push 的工作很奇怪的主要内容,如果未能解决你的问题,请参考以下文章

Promise.then(a, b) 和 Promise.then(a).catch(b) 一样吗? [复制]

09 promise then

如果使用 then ,是不是需要在 promise 中嵌套 catch?

如何对 AngularJS $promise.then 进行单元测试

使用类方法作为回调时的 Promise.then 执行上下文

为啥在 React 组件中调用了两次 Promise.then 而不是 console.log?