推送事件【译】

Posted by Neil Ning on 2023-08-02
翻译

前言

该文章翻译自web.dev的Notifications系列文章,本篇文章原文链接点击这里

到此为止,我们已经学习了如何订阅用户和如何发送消息。下一步就是接收在用户设备上接收推送的消息并展示一个通知,或者做其他我们想做的事情。

推送事件

收到消息时,会在service worker中触发推送事件,在JS中,为推送事件添加事件监听器和其他事件监听器十分相似:

1
2
3
4
5
6
7
self.addEventListener('push', function(event) {
if (event.data) {
console.log('This push event has data: ', event.data.text());
} else {
console.log('This push event has no data.');
}
});

在这个代码例子中,对接触service worker的大多数初学者来说,最令人奇怪的是self变量。self变量在Web Worker中非常常见,指代service worker本身。self的作用域是全局的,有点类似于web页面的window对象,但是对于web worker和service worker来说,它指得是这个worker进程本身。上面的例子中,self.addEventListener()可以看作是为service worker本身添加了一个事件监听器。在事件监听器内部,我们检查了event对象是否包含数据,并打印一些信息。还有其他的方式可以获取推送事件的数据:

1
2
3
4
5
6
7
8
9
10
11
// Returns string
event.data.text()

// Parses data as JSON string and returns an Object
event.data.json()

// Returns blob of data
event.data.blob()

// Returns an arrayBuffer
event.data.arrayBuffer()

根据服务端获取到的数据,大多数情况下使用json()text()即可。上面的例子眼时刻如何添加推送事件监听器,并获取数据。但是他少了一个很重要的功能,没有展示通知,也没有调用event.waitUntil()

Wait Until

理解service worker很重要的事情之一就是你没法控制service worker的运行时机。浏览器会决定什么时候唤醒service worker,什么时候终止service worker。唯一的方式是告诉浏览器,你正在做一些重要的事情,通过把一个promise对象传递给event.waitUntil()函数的方式,通过这种方式,可以让浏览器一直使service worker保持运行状态,直到promise正常resolve。
在推送事件中,有一个额外的要求是在promise正常resolve之前,你必须展示通知。下面是展示通知的一个基本的示例:

1
2
3
4
5
self.addEventListener('push', function(event) {
const promiseChain = self.registration.showNotification('Hello, World.');

event.waitUntil(promiseChain);
});

调用self.registration.showNotification()会向用户展示通知,他返回一个promise对象,当通知展示给用户后,promise对象会被resolve。
为了使上面的例子尽可能的清晰,我将这个promise对象赋值给一个变量promiseChain,然后将它传入event.waitUntil()方法。我知道这很多余,但是我看到很多由于不理解这个过程而导致的问题,他们不理解需要传给waitUntil()方法什么样的对象,或者传入一个没有reject状态的promise对象。
一个更加复杂的例子是在事件监听器中发送网络请求来获取额外数据或者跟踪推送事件用于数据分析,代码可能如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
self.addEventListener('push', function(event) {
const analyticsPromise = pushReceivedTracking();
const pushInfoPromise = fetch('/api/get-more-data')
.then(function(response) {
return response.json();
})
.then(function(response) {
const title = response.data.userName + ' says...';
const message = response.data.message;

return self.registration.showNotification(title, {
body: message
});
});

const promiseChain = Promise.all([
analyticsPromise,
pushInfoPromise
]);

event.waitUntil(promiseChain);
});

这里我们调用了一个函数pushReceivedTracking()返回一个promise,为了演示,我们模拟发送一个用户数据分析的网络请求。我们还发起了另外一个请求,利用请求的响应的数据来展示通知的标题和内容。
我们可以确保service worker保持运行,当这两个任务完成时,我们将两个promise对象合并,传给Promise.all()。将新的promise对象传给event.waitUntil()意味着我们告诉浏览器,保持service worker运行,直到这两个任务都完成,然后还需要检查通知是否正常展示。最后service worker才会运行终止。
当以上promise链reject时,浏览器会展示默认的通知。
default-notify.jpg

浏览器收到推送消息时,如果没有在推送事件中展示通知,Chrome浏览器将会展示“站点正在更新”的默认通知。
开发者收到以上异常的主要原因通常是调用只调用了self.registration.showNotification(),但是没有返回任何promise对象。这会导致浏览器展示默认通知。例如上面的例子中我们删除return语句,将代码改成如下,我们可能会看到默认的通知:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
self.addEventListener('push', function(event) {
const analyticsPromise = pushReceivedTracking();
const pushInfoPromise = fetch('/api/get-more-data')
.then(function(response) {
return response.json();
})
.then(function(response) {
const title = response.data.userName + ' says...';
const message = response.data.message;

self.registration.showNotification(title, {
body: message
});
});

const promiseChain = Promise.all([
analyticsPromise,
pushInfoPromise
]);

event.waitUntil(promiseChain);
});

你可以看到这是个很容易犯的错误。你需要记住,如果想要确保展示正常的通知,你需要保证传递给event.waitUntil()函数的promise对象正常resolve。在下一章,我们看一下如何展示不同类型的通知框。