Javascript/angular: выполнить асинхронный http из onunload

Я работаю с веб-приложением, которое блокирует ресурсы на сервере. Чтобы разблокировать их, необходимо удалить ресурс на сервере с помощью HTTP DELETE. Я знаю, что это ненадежно, и также выполняется периодическая очистка, которая разблокирует их, но цель состоит в том, чтобы разблокировать ресурс как можно скорее.

Я не могу изменить архитектуру блокировки (это не моя система), я просто должен сделать лучший удар по разблокировке.

Один момент, когда мне нужно разблокировать, — это когда вкладка или браузер закрыты. Во-первых, я обрабатываю onbeforeunload и, если документ грязный, запрашиваю у пользователя подтверждение того, что он хочет закрыть:

$window.onbeforeunload = function() {
    if (documentIsDirty) {
        return "Some prompt text";
    }
};

Я не могу разблокировать изнутри onbeforeunload, так как пользователь может отменить закрытие. Но нет никакого события (поправьте меня, если я ошибаюсь) между onbeforeunload и onunload.

Если я попытаюсь сделать вызов из onunload, то вкладка/сессия будет уничтожена, как только функция onunload вернется. Проблема в том, что это происходит до завершения http-запроса, и оказывается, что ресурс на самом деле не разблокируется.

$window.onunload = function() {
    $http.delete('/a/lock/url');

    // I've tried forcing a digest cycle to try and flush the request
    // through, but it never gets sent either way
    $rootScope.$digest();
};

Теперь я знаю, что на самом деле блокировать в Javascript - анафема, но похоже, что когда onload возвращается, это все, что она написала.

Есть ли способ заблокировать до тех пор, пока HTTP-запрос не будет фактически завершен, и предотвратить возврат onunload до тех пор?

[ОБНОВЛЕНИЕ] Решение было следующим: синхронно используйте XMLHttpRequest. Это шумно устарело, но (на момент написания) все еще работает, по крайней мере, в Chrome.

var request = new XMLHttpRequest();
request.open('DELETE', url, false);
request.setRequestHeader('X-XSRF-TOKEN', myXSRFToken);
request.send();

person Rogerborg    schedule 08.12.2016    source источник
comment
Насколько я понимаю, вы хотите выполнить и завершить какую-то операцию до закрытия браузера. Вы пытались использовать модальное подтверждение или подтверждение Javascript и выполнить закрытие самостоятельно после возврата HTTP DELETE?   -  person bhantol    schedule 08.12.2016
comment
Хм... Я видел два способа обойти это, которые не получают пощечину НИКОГДА БЛОКИРОВАТЬ запястья, например, async:false: navigator.sendBeacon Этот метод удовлетворяет потребности кода аналитики и диагностики, который обычно пытается отправить данные на веб-сервер перед выгрузкой документа. developer.mozilla.org/en-US/docs/Web/ API/Navigator/sendBeacon Другой способ — использовать WebSockets. Сервер может определить, когда сокет был отключен, поэтому вы можете выполнить разблокировку на стороне сервера. На самом деле я эффективно использовал WebSockets одним из нескольких способов.   -  person Tim Consolazio    schedule 08.12.2016
comment
Возможный дубликат JS-события onunload не всегда будет работать   -  person Heretic Monkey    schedule 08.12.2016


Ответы (2)


$http всегда будет выполнять запросы асинхронно, потому что внутри он просто использует XMLHttpRequest и всегда передает true в качестве третьего параметра функции open запроса. Из документации MDN для функции открытия XMLHttpRequest:

Необязательный логический параметр со значением по умолчанию true, указывающий, следует ли выполнять операцию асинхронно. Если это значение равно false, метод send() не возвращает значение, пока не будет получен ответ.

Если вы хотите выполнить синхронный запрос, вы можете просто использовать XMLHttpRequest напрямую и передать false в качестве третьего параметра в open. Поскольку это когда сайт закрывается, в любом случае нет необходимости использовать Angular $http.

person Scott    schedule 08.12.2016
comment
Также с той же страницы: Примечание. Начиная с Gecko 30.0 (Firefox 30.0 / Thunderbird 30.0 / SeaMonkey 2.27), синхронные запросы в основном потоке устарели из-за негативного влияния на работу пользователя. Использование устаревшая функциональность, вероятно, не является хорошей идеей в долгосрочной перспективе... - person Heretic Monkey; 08.12.2016
comment
Бинго, да, это все еще работает. Майк прав, что это шумно устарело, но это один из немногих случаев, когда мне действительно нужно это сделать. Сначала я думал, что это не работает, но мне просто нужно было передать свой X-XSRF-TOKEN, и тогда все было хорошо (кроме громкого предупреждения об устаревании). - person Rogerborg; 09.12.2016

Недавно столкнулись с той же проблемой при выполнении такого рода действий и решили ее с помощью navigator.sendBeacon(). Метод navigator.sendBeacon() асинхронно отправляет небольшой объем данных через HTTP на веб-сервер. Для последнего браузера вы можете сделать как

window.addEventListener('beforeunload', function (event) {
    data = new FormData();
    // for CSRF Token
    token = $('meta[name="csrf-token"]').attr('content');
    data.append("key", value);
    data.append("authenticity_token", token);
  
    navigator.sendBeacon("URL", data);
    
    // Cancel the event as stated by the standard.
    event.preventDefault();
    // Chrome requires returnValue to be set.
    event.returnValue = 'Are you sure you want to leave this page without saving?';
  });

Для получения более подробной информации ознакомьтесь с Navigator.sendBeacon() и Окно: событие перед выгрузкой

person Mayur Shah    schedule 27.07.2020