JavaScript Autosave: Wie führe ich Code vor dem Seiten-Refresh aus?

Kokujou

Lieutenant
Registriert
Dez. 2017
Beiträge
929
Hallihallo!

Ich habe aktuell eine Applikation und möchte dem Nutzer das Speichern der Form durch Auto-Speichern ersparen. Also quasi live-edit. ähnlich wie hier bei computerbase, wo ein text den man eingibt gespeichert wird während man schreibt.

die Sache ist: es handelt sich um viele Aktionen die man schnell hintereinander tätigen kann, wenn ich wann immer der Nutzer etwas ändert den update-request schicken würde. Und dann würden sich die konkurrenten Update-Requests in die quere kommen, was nicht so schön ist.

Also würde ich gerne alle 10 Sekunden einen update call machen (z.b.). Das Problem. Wenn der nutzer die Seite verlässt ist das Model natürlich tot. Also muss ich bei jeder Art des Seite verlassens den Web request zum speichern schicken.

Und hier ist die frage. Denn ich hab schon das unload event gefunden, aber dann wird ja immer gleich ein dialog angezeigt und das ist mir zu penetrant. ich hab schon alles möglcihe versucht um diesen Dialog zu unterdrücken, mit return null oder return false oder preventDefault oder stopPropagation.

fällt euch dazu was ein?
 
Warum musst du auf das Verlassen reagieren? Was meinst du das Model ist dann weg?
ComputerBase schickt nach einer Weile einen Draft per POST. Da kann der Strom ausfallen, und die Änderung sind dennoch bei ComputerBase noch im System.

1625754382556.png
 
  • Gefällt mir
Reaktionen: madmax2010
Websocket, da kannst du nach jedem Input die Daten senden, aber der aufwand lohnt sich nur dafür meiner Meinung nach nicht. Aber ist nicht ganz so teuer wie ein extra Ajax Call.

Oder eben das Autospeichern throttlen, wie es auch CB macht. Es wird nur gespeichert wenn der grüne Kreis nach ein paar Sekunden stillstand kommt. Wenn du zwischen dem speichern die Seite zu machst sind die Daten trotzdem weg.

Beforeunload kannst du dir angucken (weil unload laut MDN deprecated ist), da geht das wohl. Aber ich würde mich nicht drauf verlassen das eine asynchrone Aktion mit 100%iger Sicherheit auch komplett ausgeführt wird.

Eventuell mit nem Throttle arbeiten und den aktuellen Status mit jedem Event im localStorage wegspeichern. So kannst du zumindest im aktuellem Browser den letzten Status wiederherstellen, auf nem anderem Gerät sind die Daten dann eben was älter.


Kann auch gut sein das ich BeforeUnloadEvent API: User interaction required for dialog box falsch verstanden hab, die grünen Browser heißen mit Sicherheit "ja, der User muss clicken" und nicht "ja, kann man umgehen".
Aber wenn du eine eigene Nachricht reinschreiben kannst ist das ja nicht ganz so schlimm.
Dann weiß der User warum das Popup kommt.


Edit: guckt dir mal die Beacon API an
Das ist quasi Fetch ohne das der Browser auf eine Antwort wartet, kann gut sein das du damit bessere Chancen hast.

Edit edit: und wenn man weiter googlet stößt man auf Page Visibility das noch vor unload getriggert wird.
 
Zuletzt bearbeitet:
Warum nicht im Local Storage hinterlegen und ggf. im Intervall hochschieben?
 
  • Gefällt mir
Reaktionen: KitKat::new(), Kokujou, dflt und eine weitere Person
Denke der Ansatz von Yuuri ist der einfachste und effizienteste, ohne genauere Details von deinem Setup zu kennen.
 
  • Gefällt mir
Reaktionen: kim88
Richtig, genaue Anforderungen sind nicht bekannt.

LocalStorage hat durchaus Vorteile. Aber bei ComputerBase kann ich den Browser wechseln und dennoch weiter schreiben.

Statt Intervall würde ich wohl einfach auf Änderungen reagieren zusammen mit einem debouncing, z.B. von 1s.
 
localstorage ist tatsächlich eine gute idee!
so kann ich das modell im localstorage live updaten und wann immer ich wieder auf die Seite komme den rest request machen, falls was im localstorage ist.

einziger Nachteil: wenn du die Seite nicht mehr besuchst (was bei mir ja meistens nicht der Fall ist) dann gehen die daten verloren.

WebSockets stehen übrigens tatsächlich auf meiner Todo-Liste. Aber eher etwas weiter hinten, da ich gern erstmal die grundfunktionalität zum Laufen kriegen würde bevor ich mit Optimierungs-Geschichten anfange.

ich wusste aber nicht dass websockets auch in diesem Fall weiterhelfen. Bei Websockets denke ich eher an Nachrichten Server-Client, die Requests Client->Server würde ich lieber nach wie vor über die REST-API schicken weil ich das sauberer finde
 
Socket deshalb weil die Verbindung zum Server aufrecht gehalten wird, daher etwas leichter als ein kompletter HTTP Request den man öfter senden könnte. Aber der Socket selbst hat overhead, wenn du die Verbindung also nicht an einer anderen Stelle sowieso schon brauchst lohnt sich das nicht wirklich.
Wenn man oft Events sendet bieten sich Sockets an, ob man nur wegen Autosave Sockets zu potenziell 1000 Usern aufrecht halten muss steht auf nem anderem Blatt (bis auf ein paar Aufnahmen eher nein)

Aber debounce/throttle nach Input-Events ist 99% des Wegs. Und für den Fall das der User den Tab schließt bevor der Debounce fertig ist kannst du immer noch mit visibilitychange & Beacon hinterhersenden wenn er die Seite verlässt.
 
Zuletzt bearbeitet:
Kokujou schrieb:
einziger Nachteil: wenn du die Seite nicht mehr besuchst (was bei mir ja meistens nicht der Fall ist) dann gehen die daten verloren.
Oder du so etwas verrücktest machst, wie die Seite mal mit einem anderen Browser zu besuchen, wofür es diverse Ursachen geben kann.
 
Joshinator schrieb:
visibilitychange & Beacon
ich hab angebissen, kläre mich auf was du damit meinst :)

tollertyp schrieb:
Oder du so etwas verrücktest machst, wie die Seite mal mit einem anderen Browser zu besuchen, wofür es diverse Ursachen geben kann.
touche... meine backend-logik ist immerhin so gehalten dass ich als update-request nicht das gesamte objekt senden muss, ich könnte also auch nur die geänderte property schicken.
das problem ist halt, dass sich dann trotzdem noch Request in die Quere kommen die dieselbe property senden.

Man stelle sich vor: Nutzer tippt A -> property wird mit A geändert, request läuft, Nutzer tippt B -> Property wird mit AB geändert, request läuft, request fertig, neuer Wert AB -> property change zu A request fertig -> Property ist plötzlich A.



was mir spontant noch einfällt wäre vielleicht die Backend-Logik zu ändern. Man könnte ja mit dem Request einen Timestamp mitsenden, dann müsste man aber zu jedem Model auch einen last-updated timestamp mitsenden der in die Millisekunden ebene geht. und das ist halt meh. oder man macht son spaß wie Millisekunden seit Januar oder so. dan wärs nur ein einfacher integer... und dann nur die update requests durchlässt, die neuer sind und alle älteren einfach verschluckt. aber auch da gibts sicherlich lücken. wenn du z.B. property A zu Zeitpunkt 1 änderst und Property B zu zeitpunkt 2 änderst würde er property A verschlucken

oder man sendet den alten und den neuen wert und die Datenbank gleicht ab ob der alte Wert auch wirklich aktuell ist...
 
Schick halt notfalls einen Zeitstempel mit und merke dir den letzten gespeicherten Zeitstempel. Und wenn der erhaltene Zeitstempel älter ist, kannst das Event ignorieren.
 
ich hab jetzt mal die oldProperty mitgeliefert und das sieht gar nicht mal so schlecht aus, recht robust.

dann jetzt die abschließende Frage:
sieht jemand einen denkfehler in der logik? Dass man immer das alte user objekt mitschickt und die werte nur ändert wenn es syncrhon mit dem in-DB objekt ist? das in kombination dass ich wirklich nur die geänderten properties sende.

und noch eine frage zum visibilitychange event: wird der page unload beim visibilitychange denn herausgezögert bis der asynchrone request fertig ist? oder könnte es sein dass der dann einfach abgebrochen wird?
 
Kokujou schrieb:
wird der page unload beim visibilitychange denn herausgezögert bis der asynchrone request fertig ist? oder könnte es sein dass der dann einfach abgebrochen wird?

https://developer.mozilla.org/en-US/docs/Web/API/Beacon_API
Steht direkt im erstem Satz:
The Beacon API is used to send an asynchronous and non-blocking request to a web server. The request does not expect a response. Unlike requests made using XMLHttpRequest or the Fetch API, the browser guarantees to initiate beacon requests before the page is unloaded and to run them to completion.

Wenn du visibilitychange in Kombination mit den normalen Ajax APIs benutzt, dann nein wird nicht garantiert.
Das macht nur die Beacon API, du sagst dem Browser "egal was der User macht, send zumindest noch den Request, die Antwort will ich gar nicht".
 
Habe ich dich richtig verstanden? Du schickst den alten und den neuen Wert und wenn der alte Wert mit dem gespeicherten Wert übereinstimmt übernimmst du dann in der Datenbank den neuen Wert?

Wenn ich das richtig verstanden habe, dann ist das ziemlicher Murks.

Warum?
Sowohl bei Paketverlust oder bei falscher Reihenfolge hast du nicht mehr das in der Datenbank stehen, was du willst.

Beispiel 1: Paketverlust
  • Nutzer gibt "A" ein. Event mit neuem Wert="A" und altem Wert="" wird gefeuert, "A" ist gespeichert.
  • Nutzer ändert zu "AB". Event mit neuem Wert="AB" und altem Wert="A" wird gefeuert, geht verloren.
  • Nutzer ändert zu "ABC". Event mit neuem Wert="ABC" und altem Wert="AB" wird gefeuert. Server erhält Event und merkt "AB" ist nicht das, was ich in der Datenbank habe. Wert "A" bleibt weiterhin in Datenbank.

Beispiel 2: Falsche Reihenfolge beim Paketeingang
  • Nutzer gibt "A" ein. Event mit neuem Wert="A" und altem Wert="" wird gefeuert, "A" ist gespeichert.
  • Nutzer ändert zu "AB". Event mit neuem Wert="AB" und altem Wert="A" wird gefeuert, kam aber noch nicht an
  • Nutzer ändert zu "ABC". Event mit neuem Wert="ABC" und altem Wert="AB" wird gefeuert. Server erhält Event und merkt "AB" ist nicht das, was ich in der Datenbank habe. Wert "A" bleibt weiterhin in Datenbank.
  • Nun kommt das Event mit "AB" an. In der Datenbank wird der Wert zu "AB" geändert.

Aber warum etwas einfaches wie einen Timestamp nehmen...

Oder meinst du, dass in jedem Event der Wert mitgeschickt wird, der gerade in der Datenbank gespeichert sein müsste, den es also aus einer Antwort vom Server erhalten hat? Was ist, wenn die Antwort vom Server dann verschluckt wurde? Dann geht der Client immer vom falschen alten Wert aus...
Aus meiner Sicht alles viel komplizierter als eine Timestamp-Prüfung.

Statt einem Zeitsptempel könnte es auch einfach eine fortlaufende Nummer sein... einfach ein einfacher Indikator, um die Events zeitlich einordnen zu können.
 
zu der timestamp sache: Was wenn du zwei verschiedene properties änderst?
wenn ich das single-property-change pattern von dem ich erzählt habe benutze ( was ja theoretisch sinnvoller ist, allein schon um den datenverkehr zu minimieren), dann könnte folgendes passieren:
Zeitpunkt 1: Sende Property A -> Zeitpunkt 2: Sende Property B -> Zeitpunkt 2 vollständig -> Zeitpunkt 1 kommt an und wird verschluckt.

also müsste ich quasi pro property einen timestamp bauen, was schonmal wieder nicht so sexy wäre.

Aber vielleicht brauche ich das ja alles nicht, denn wenn der visibilityChange in Kombination mit sendBeacon funktioniert - was echt geil wäre - dann ist das am performantesten. ich glaub das probier ich aus :) danke!
 
Wieso willst du die einzeln senden?
Wenn du 5 Textboxen hast kann jede Textbox den selben Debounce auslösen. Erst wenn 10sec Ruhe ist wird der PUT/PATCH gesendet um die Daten online zu haben.

Put wenn du einfach alles auf einen Schlag updaten willst weil weniger Arbeit.
Patch wenn du die Props senden willst die sich geändert wurden, musst aber eben am Rechner den Diff zwischen aktuell und letztem Sync bilden (was jetzt auch nicht die Welt ist).

Nur auf visibilityChange zu hören ist auch keine 100%ige Lösung.
Wenn ich am Desktop war und aufm Klo am Handy weitermache dann sind die Daten noch nicht am Server angekommen wenn der Tab am Desktop offen bleibt.

Debounce die Input-Events, send die Daten nach Xsec an den Server, und wenn der User innerhalb des Debounce den Tab schließt kannst du den Sync ja mit der Beacon API nochmal anstoßen.
Timestamp kannst du dann ja immer noch einmalig je Sync wegspeichern.


Code:
User tippt in input1
// starte 10sec debounce
User tippt in input2
// reset debounce
User macht 20sec nix
// debounce läuft nach 10sec aus, sendet die aktuellen Daten (ob jetzt Put/Patch ist egal)
User tippt in input1 weiter
// starte 10sec debounce
User macht nach 5sec CTRL + W
// visibilityChange mach Put/Patch mit Beacon API damit die auch noch gesynct werden
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: KitKat::new()
Joshinator schrieb:
Wieso willst du die einzeln senden?
Wenn du 5 Textboxen hast kann jede Textbox den selben Debounce auslösen. Erst wenn 10sec Ruhe ist wird der PUT/PATCH gesendet um die Daten online zu haben.

Put wenn du einfach alles auf einen Schlag updaten willst weil weniger Arbeit.
Patch wenn du die Props senden willst die sich geändert wurden, musst aber eben am Rechner den Diff zwischen aktuell und letztem Sync bilden (was jetzt auch nicht die Welt ist).

Nur auf visibilityChange zu hören ist auch keine 100%ige Lösung.
Wenn ich am Desktop war und aufm Klo am Handy weitermache dann sind die Daten noch nicht am Server angekommen wenn der Tab am Desktop offen bleibt.

Debounce die Input-Events, send die Daten nach Xsec an den Server, und wenn der User innerhalb des Debounce den Tab schließt kannst du den Sync ja mit der Beacon API nochmal anstoßen.
Timestamp kannst du dann ja immer noch einmalig je Sync wegspeichern.


Code:
User tippt in input1
// starte 10sec debounce
User tippt in input2
// reset debounce
User macht 20sec nix
// debounce läuft nach 10sec aus, sendet die aktuellen Daten (ob jetzt Put/Patch ist egal)
User tippt in input1 weiter
// starte 10sec debounce
User macht nach 5sec CTRL + W
// visibilityChange mach Put/Patch mit Beacon API damit die auch noch gesynct werden
darum würd ich mir das debounce komplett schenken und einfach interval calls durchführen alle 10 sekunden. das ist leichter als ne Debounce Logik einzubauen. Und das kombinier ich dann mit dem visibility-change :)
 
Javascript:
let timer = null
let tippEventHandler = function() {
  clearTimeout(timer);
  timer = setTimeout(function(){
    console.log('weils halt wirklich nur 6 zeilen code sind die nicht alle 10sec irgendwas sinnloses machen');
  }, 10000);
}
 
  • Gefällt mir
Reaktionen: mental.dIseASe und floq0r
Also du hast Bedenken bzgl. des Datenverkehrs, willst aber keine einfache Debounce Logik?
Die Wahrscheinlichkeit, dass du die Events überhaupt synchronisieren musst, ist bei einem Debounce eh schon gering.
 
  • Gefällt mir
Reaktionen: KitKat::new()
Zurück
Oben