在 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.data
是IDeviceData
,我建议使用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) 一样吗? [复制]
如果使用 then ,是不是需要在 promise 中嵌套 catch?
如何对 AngularJS $promise.then 进行单元测试