PAYATHON 2026

Expo 多 push token 消息返回的 push tickets 是否保证顺序?

数据阿明

可以按消息和接收者的顺序建立对应关系:先遍历消息数组,再按 to 中的顺序展开每条消息的接收者。返回的 tickets[i] 对应展开后的第 i 个接收者。Expo 官方文档说明,tickets 按发送顺序返回;其示例中的三个消息对象包含四个接收者,返回了四张 ticket。因此,方式 B 可以用于关联 push token 和 ticket。Expo 官方文档

为什么 ticket 数量可能多于消息对象数量?

当 to 是数组时,同一个消息对象会向多个接收者发送通知,每个接收者都有自己的 ticket。例如:

const messages = [
  { title: 'Hello', to: [tokenA, tokenB] },
  { title: 'News', to: [tokenC] },
];

对应关系为:

tickets[0] → messages[0] → tokenA
tickets[1] → messages[0] → tokenB
tickets[2] → messages[1] → tokenC

因此,不能直接按相同索引关联 messages[i].to 和 tickets[i],需要先展开接收者。

SDK 会按接收者计算实际通知数量,并检查返回的 ticket 数量是否匹配。chunkPushNotifications() 也会遍历 to 数组,必要时将同一条消息的接收者拆到不同批次。因此,建立映射时应以实际发送的批次为单位。SDK 源码

如何在方式 B 中保存对应关系?

可以保留现有的多接收者消息结构。先调用 chunkPushNotifications(),然后展开每个批次中的接收者,与该批次返回的 tickets 配对:

import { Expo } from 'expo-server-sdk';

const expo = new Expo();

async function sendWithTicketMapping(messages) {
  const results = [];
  const chunks = expo.chunkPushNotifications(messages);

  for (const chunk of chunks) {
    const targets = chunk.flatMap((message) => {
      const tokens = Array.isArray(message.to)
        ? message.to
        : [message.to];

      return tokens.map((pushToken) => ({ pushToken }));
    });

    const tickets = await expo.sendPushNotificationsAsync(chunk);

    if (tickets.length !== targets.length) {
      throw new Error('Ticket count does not match recipient count');
    }

    const mapped = tickets.map((ticket, index) => ({
      pushToken: targets[index].pushToken,
      ticket,
    }));

    results.push(...mapped);
  }

  return results;
}

这里假设传入的消息符合 Expo 要求。返回结果中的每一项都保留了 pushToken 和完整的 ticket,可按业务需要持久化。示例中的数量检查可以防止数量异常时继续配对,但不能独立证明服务端的返回顺序。

如果还要关联用户或业务消息,应在发送前另行保存这些标识与接收者的关系。同一个 token 可能收到多条消息,因此不能只用 token 作为每次发送记录的唯一键。

哪些操作会破坏映射?

  • 先过滤失败的 tickets,再按索引配对。错误 ticket 也占据对应位置,必须先完成关联,再处理 status。失败的 ticket 可能没有 id。ticket 格式说明
  • 按并发请求的完成顺序拼接 tickets。不同批次可能先后返回,应在各自批次内部配对,再汇总映射结果。
  • 某个批次失败后,继续用全局索引匹配剩余结果。请求抛出异常时,应针对该批次处理失败,避免后续结果整体错位。

对于 status === 'ok' 的 ticket,可以保存 ticket.id → pushToken 的关系。后续 getPushNotificationReceiptsAsync() 返回的是以 receipt ID 为键的对象,应按 ID 查询,不能再依赖数组位置。ticket 的 ok 表示 Expo 已接收通知,receipt 的 ok 表示推送提供方已接收,均不能据此认定设备已经收到通知。push receipts 说明

备注:内容仅供参考。