■20/12/25 1:06AM
Promise
仕事は炎上芸なり~と勤しむ炎上芸人にも分かるように解説をさせて頂きたく候
(勤しむところはソコではない)
///promise/async/await 非同期関数
【ES6】 JavaScript初心者でもわかるPromise講座 - Qiitahttps://rightcode.co.jp/blog/information-technology/javascript-promisehttps://rightcode.co.jp/blog/information-technology/javascript-async-awaithttp://www.tohoho-web.com/ex/promise.htmlhttps://sbfl.net/blog/2016/07/13/simplifying-async-code-with-promise-and-async-await/https://qiita.com/niusounds/items/37c1f9b021b62194e077https://qiita.com/soarflat/items/1a9613e023200bbebcb3非同期関数は処理の順序を制御できない問題があった、そこでPromise↓Promise オブジェクトは then(ok_callback, ng_callback) というメソッドを持ちます。then() はPromise が成功または失敗になるまで処理を受け流し、処理を.then()で繋げ順番を確保することが可能成功時に ok_callback を、失敗時に ng_callback をコールバック関数として呼び出します.then() は第一引数に成功時のコールバック関数、第二引数に失敗時のコールバック関数.catch(ng_callback) は、.then(undefined, ng_callback) と同じ意味.catch() は処理中に発生した throw をキャッチできるES2018(ES9) では、.finally() がサポートされましたfunction aFunc3(data) { return new Promise(function(okCallback, ngCallback) { setTimeout(function() { if (Math.random() < 0.30) { ngCallback(new Error('ERROR!')); } else { okCallback(data * 2); } }, Math.random() * 1000); });}function sample_finally2() { aFunc3(100).then((data) => { console.log(data); return aFunc3(data); }) .then((data) => { console.log(data); return aFunc3(data); }) .then((data) => { console.log(data); throw new Error('ERROR!!!'); }) .catch((e) => { console.log("catch"); console.log(e); }) .finally(() => { console.log('*** Finally ***'); });}//200 400 800 catch Error:ERRROR!!! *** Finally ***Promise.all() は配列で指定された全てのPromiseタスクを待ち全てが完了した時点で .then()を呼ぶPromise.race()ならいずれかのPromisefunction sample_all() { p1 = taskA(); p2 = taskB(); Promise.all([p1, p2]).then(() => { console.log("taskA and taskB are finished."); });}↓ES2017 では、async/await がサポートされましたasync と await を用いることで、Promise に対応した非同期関数を、同期関数の様にシンプルに呼び出すことが可能となります同期関数の様に呼び出したい非同期関数を呼び出す際に await をつけます。await を呼び出す関数に async をつけますasync function sample_async_await_with_catch() { var val = 100; try { val = await aFunc3(val); console.log(val); val = await aFunc3(val); console.log(val); val = await aFunc3(val); console.log(val); } catch (e) { console.log(e); }}
■コールバック関数広い定義でいうと「高階関数に渡すための関数」「関数を受け取る関数」は「高階関数」、つまりhello()がコールバック関数// 関数を2回実行する関数!!function doTwice(func) { func(); // 1回目Hello! func(); // 2回目Hello!}// あいさつするだけの関数function hello() { console.log('Hello!');}// あいさつを2回実行するdoTwice(hello);
========================================
もっと詳しく、もっと分かり易く、どう使うか↓
https://knowledge.sakura.ad.jp/24890/https://jsprimer.net/basic/async/https://dev.classmethod.jp/articles/javascript-asynchronous-processing/■処理の繋がり1)コールバック関数 ある関数の処理が終われば次のコールバック関数を呼ぶという指定がそれ 歴史的にはエラーファーストコールバック(のルール) 処理が失敗した場合は、コールバック関数の1番目の引数にエラーオブジェクトを渡して呼び出す 処理が成功した場合は、コールバック関数の1番目の引数にはnullを渡し、2番目以降の引数に成功時の結果を渡して呼び出す fs.readFile("./example.txt", (error, data) => {
2)Promise(非同期処理に対するPromise→順番を合わせる意味では同期処理ではと思う?JSはシングルスレッドかつ非同期という糞?仕様) ある関数の処理が終わればPromiseオブジェクトを返す JSがシングルスレッドだが 処理を一定の単位ごとに分け処理を切り替えながら実行する並行処理(concurrent)の仕様のため 順序を考慮する必要がある 非同期処理の実行中にとても重たい処理があると非同期処理の切り替えが遅れる
Promiseオブジェクトは3つの内部状態を持ちます。 pending(保留): まだ非同期処理は終わっていない(成功も失敗もしていない) fulfilled(成功): 非同期処理が正常に終了した rejected(拒否): 非同期処理が失敗した 初期状態はpendingで、一度fulfilledまたはrejectedになったらそれ以降は状態は変わらず、非同期処理の終了時に返す値もそれ以降は変わらない
Promiseのコンストラクターは関数を引数に取って、その関数がさらに2つの関数を引数に取る 1番目の関数(resolve)に引数を渡して実行すると状態がfulfilledになり、引数の値はPromiseオブジェクトが保持する値になる 2番目の関数(reject)に引数を渡して実行すると状態がrejectedになり、引数の値はPromiseオブジェクトが保持する値になる 関数が例外を投げた場合も状態がrejectedになり、投げた値がPromiseオブジェクトが保持する値になる、throwする値をrejectedに渡して実行した時と同じ
then()は2つの関数を引数に取り、Promiseの状態がfulfilledになったら1番目の関数が、rejectedになったら2番目の関数が実行されます。 then()の1番目の引数が関数でなければidentity function(入力値をそのまま返す関数)が代わりに使われます 2番目の引数が関数でなければthrower function(入力値を例外として投げる関数)が代わりに使われます catch()は1番目の引数にidentity functionを指定したthen()と同じ
上の挙動をオレオレPromiseをYakusokuで作っているので分かり易い https://knowledge.sakura.ad.jp/24890/
なお、本質としてはコレ、下記ソースが決まりの流れ、ひな形としてヤリ慣れるしか1)時間が掛かる処理をPromise化して順序立てよう2)成功と失敗のコールバックを指定しよう
//処理にコールバック関数を入れて成功と失敗時の型で終えるfunction dummyFetch(cmt, callBack) { setTimeout(() => { if (cmt.startsWith("/success")) { callBack(null, { body: `Response body of ${cmt}` }); } else { callBack(new Error("Bad")); } }, 1000 * Math.random());}//プロミスを入れるためラッパーを関数にかますfunction aaaFilePromise(cmt) { return new Promise((resolve, reject) => { dummyFetch(cmt, (err, data) => { if (err) { reject(err); // 失敗: 内部状態をrejectedにする } else { resolve(data); // 成功: 内部状態をfulfilledにする } }); });}//プロミスチェーンでのフローaaaFilePromise("/success/passwd") .then((data) => { // 読み出しに成功したらresolve()に渡した値が引数として渡される console.log("1", data); //return 'next';//テキストがあってもなくても次のthenに行く、省略でもテキストでも第一引数関数に行く成功側 return aaaFilePromise("/etc/text");//エラーで次のthenの失敗側の第二引数関数にきっちり行く }) .then((data) => { console.log("2", data); return aaaFilePromise("/success/shadow1"); }, (data) => { console.log("2e", data); return aaaFilePromise("/success/shadow2"); }) .then((data) => { console.log("3", data); return aaaFilePromise("/etc/shadow"); }) .catch((err) => { // reject()に渡した値が引数として渡される console.log("error", err); });
then()/ catch()は、引数で渡された関数の戻り値から新たにPromiseオブェクトを作り、そのオブジェクトを返します。そのためメソッドチェーンが可能 引数に渡した関数の戻り値がPromiseオブジェクトの場合はそのオブジェクトをそのまま返す、そうでなければ戻り値をPromiseで包んで返す エラーでキャッチに飛ぶ訳ではなく次のthen第2引数関数に飛んでいる、省略でcatchに行っているように見えるだけ
dexieやPWAでの提供があり使う(処理を順序立てて使うようプログラムを組む時にdexie: db.schedule.where('site').equals('sche').first().then(function(records) {pwa: caches.keys().then(function(keyList){return Promise.all(keyList.map(function(key){
3)async / await
promiseは順番決めができたがasync/awaitは順番を扱う処理もできる
setTimeout/setIntevalがプロミスチェーンだけでは時間を止められない
シングルスレッドから似非スレッドで分離し非同期になるから、awaitを入れると同期する↓
const wait = (sec) => { return new Promise((resolve, reject) => { setTimeout(resolve, sec*1000); });};async function arrKick_async(arr) { for(let i=1; i<=num_arr; i++){ arr = await kickPromise(arr); await wait(2);
}}arrKick_async(arr);
========================================JSネイティブとPromiseとasyncが混ざった場合は同期しない、then()すら超えてくる↓
4)コールバック地獄
結局コールバック地獄が扱いやすい(スレッドの切り替えがなければ同期ができる)、最近のJSフレームワークは全部Promise化しているらしいが
例)キャッシュを保存し、そのステータスを取るようにAsyncやPromiseで保存待ちの順番をにしても、待たない
//隙間がないと1度エラーだとエラーになりっぱなしlet num_cache;num_cache = getCacheStatus();if(num_cache == 0 || !num_cache){ num_cache = getCacheStatus(); if(num_cache == 0 || !num_cache){ num_cache = getCacheStatus(); if(num_cache == 0 || !num_cache){ num_cache = getCacheStatus(); if(num_cache == 0 || !num_cache){
//setTimeoutで隙間があっても関数スレッドの返り値を代入するスレッド切替時に、返り値を待つスレッドの方は次の処理に進んでしまいIF判定ができないlet s = setTimeout(function(){ let num_cache1 = getCacheStatus(); if(num_cache1 == 0 || !num_cache1){ s = setTimeout(function(){ let num_cache2 = getCacheStatus(); if(num_cache2 == 0 || !num_cache2){ s = setTimeout(function(){ let num_cache3 = getCacheStatus(); if(num_cache3 == 0 || !num_cache3){ s = setTimeout(function(){ let num_cache4 = getCacheStatus(); if(num_cache4 == 0 || !num_cache4){
//Func返り値やPromiseやAsyncでのスレッドの切り替えがないDOMの判定であれば上手くいくlet s = setTimeout(function(){ getCacheStatus(); if(document.getElementById('mes_filenames').innerHTML == 'none'){ s = setTimeout(function(){ getCacheStatus(); if(document.getElementById('mes_filenames').innerHTML == 'none'){ s = setTimeout(function(){ getCacheStatus(); if(document.getElementById('mes_filenames').innerHTML == 'none'){ s = setTimeout(function(){ getCacheStatus(); if(document.getElementById('mes_filenames').innerHTML == 'none'){ s = setTimeout(function(){ getCacheStatus(); if(document.getElementById('mes_filenames').innerHTML == '<?php echo $lang_page->install_none; ?>'){
function getCacheStatus(){ let num_caches = 0; let num_success = 0; caches.keys().then(function(keyList){ return Promise.all(keyList.map(function(key){ caches.open(key).then(function(cache) { cache.matchAll().then(function(response) { document.getElementById('mes_filenames').innerHTML = ''; let s; let o; for(const value of response){ s = value.status; o = value.ok; document.getElementById('mes_filenames').insertAdjacentHTML('afterbegin', value.url + '<br>'); if(s == '200' && o){ num_success++; } num_caches++; } if(num_caches > 0){ document.getElementById('mes_progress_rate').innerHTML = 'Progress: ' + num_success / num_caches * 100 + '%'; }else{ document.getElementById('mes_filenames').innerHTML = 'None'; } }); }); })); }); return num_caches;}===============
thenの入れ子だと親の部分だけ先に進んでしまう、入れ子ダメで親子を作れば親→子の一方方向で子で終わるトーナメント構造で(上がらない)
test1().then((result) => {
test2().then((result) => { //fuok });})then(function() これは入れ子
↓
test1().then((result) => {
test2().then((result) => { //fuok
})then(function(){ //fuok2 }); これでトーナメント構造
})catch(function(e)
promiseチェーンでthen毎にに欲しい引数を出すが、複数であればそれらの引数をthenに渡せない、下記1は駄目
1)thenで一つの引数になるようにロジックを組む(thenのトーナメント構造、一階層上で変数に入れる等) }).then(function(response){ return [response.json(), arr_del]; }).then(function(v) { json = v[0]; arr = v[1];
2)callbackとresolveに配列を使うとOK、オブジェクトでもいいかも
function dummyFetch(cmt, callBack) { if(Array.isArray(cmt){ var p = cmt[0]; var s = cmt[1]; }else{ var p = cmt; var s = 1; } setTimeout(() => { if (p.startsWith("/success")) { var r = [p, ++s];//これ不可[p, s++] callBack(null, r); } else { callBack(new Error("Bad")); } }, 1000 * Math.random()); } function aaaFilePromise(cmt) { return new Promise((resolve, reject) => { dummyFetch(cmt, (err, data) => { if (err) { reject(err); } else { if(Array.isArray(data){ var p = data[0]; var s = data[1]; }else{ var p = data; var s = 1; } resolve([p, s]); // 成功: 内部状態をfulfilledにする } }); }); }===============
Promise化していない関数を使いたいが、そのまま使うかthen化できるようにするか? 1)次thenに進みたい元Funcの処理としてresolveの返り値に入れる、ダメなら省略可だがreject()に渡しcatchする
2)次thenには適当でもいいのでreturnで進む function test1 () { return new Promise((resolve, reject) => { const a = 1; const b = 2; resolve([a, b]); }) } test1().then((result) => { console.log(result[0]); // 1 console.log(result[1]); // 2
return 'go next'; }).then(function(){
エラーハンドリングしたい、よくわからんが下記で動作に違いがでた、rejectはJSがエラーを吐いた
1)catchさせるにはthrow new Errorし、alertを出す
2)catchはしないが次のthenには移動させないためreturn false
}).then(function(json){ if(json.init == 'Not appropriate access'){ throw new Error('Server warning'); }else if(json.init == 'No data'){ //reject("initiate!"); return false; }else{
resolve(json);
} }).catch(function(error){ alert('Ooops: ' + error); });Promise.allを使って、3つのpromiseを同時に実行、allはすべての非同期処理がresolveされたタイミングで結果を返 Promise.all([test1, test2, test3]).then(function() { console.log("Fourth");もっと簡単に async, await, Promise - Qiita
■Javascript
https://www.bangboo.com/cms/blog/page_325.html
Comment (0)
■20/4/21 12:00AM
Dexie
Indexeddbを使うならラッパーが要るやろ、とオモて、溺死やったらコレ便利やんってちゃうか、とオモて、知らんけど
■構造 DB > Table > kvs > record(db=)schedule_db > schedule(=table) > kvs(Key=自動採番:Value=json=record)
kvsはid++が先頭に来ずでこう→ 1:"{"name":"aaa",reg_date":"20201027_11:57:24","id":1}"
var db = new Dexie("schedule_db");db.version(1).stores({schedule: '++id,name,reg_date'});
■操作var db = new Dexie("schedule_db"); schedule_dbというDBがセットされschedule: '++id,name,key,reg_date' テーブルscheduleにカウントアップKey:JSON{id,name,key,reg_date}が入るもしschedule: 'name,key,reg_date'ならnameが自動で一番最初のカラムだからキーになるキーの値が同じだとAddができない
stores()で一番最初に来るのが「主キー」put()は追加しあれば更新、add()は追加のみで同キーがあればエラー
put()はupdateとしてDB上上書きされるように見えるがループすると全データが出てくる、謎first()やlimit()やlast()で欲しいレコードを取得toArray()ではobjが返るがobjは配列で引数0をつけてアクセス obj[0]get('aaa')はkey=aaaの値を持つ最初の行、get({key: "sss", value: "ccc"})で条件付可delete()の返り値Promiseに削除件数が入っている
■削除のレベルは行、表、DB行削除 db.schedule.where({id: id}).delete().then (function(records){表削除 trancateで db.schedule.clear(); コンソールには反映されていないがレコード削除済 db.table(storeName) で操作あるいはtables ->だめだった 表を複数持てるdb.version(1).stores({ genres: '++id,name', albums: '++id,name,year,*tracks', bands: '++id,name,*albumIds,genreId'});db.delete() DBを消せる(その後新たに再作成できる)
■insertdb.schedule.add({name: "aaa", key: "bbb", reg_date: getCurrentTime()}).then (function(id){ return db.schedule.get(id);}).then(function (schedule) { alert ("Date was set at " + schedule.reg_date);
■select
db.reserve.each(function(records){
if(records == null || records == ''){ alert ("No data found"); }else{ records.json;
toArrayは複雑になる、eachの方がよいかも、toArrayとeachの入れ替えてのselect発行が基本できるみたい
db.reserve.where({flg_del: 2}).toArray(function(records){ records.forEach(function(record){//obj.forEach直で行ける Object.keys(record).forEach(function(key) {//直で行けずObject.keys().forEach()で let val = this[key]; if(key == 'json'){ let v = JSON.parse(val);//直で行けずパースが必要 Object.keys(v).forEach(function(k) { let v = this[k]; console.log(k, v); }, v); } }, record); });
複雑なものはOr句で出せる
db.reserve.where('reg_date').below(getCurrentTime()).or('flg_del').equals(2).limit(3).each(function(records){ console.log('List: ' + JSON.stringify(records));
And句はfunctionを取るが簡単な感じがする
db.reserve.where('datetime').below(display_expire_date).and(item => item.flg_del == 2).desc('datetime').limit(display_ex).each(function(records){
複数条件はwhereにオブジェクトとして記載するがbelow等のフィルターにつながらずエラー、シンプルならokだが
db.reserve.where({datetime, flg_del: 2}).below(display_expire_date).limit(display_ex).each(function(records){
複数条件にフィルターをつけるにはwhereに配列で記載するが一つはbelow、一つはequalsでフィルタが複数でうまくいかない、シンプルならokだが
db.reserve.where(["datetime", "flg_del"]).below([display_expire_date, 2]).limit(display_ex).each(function(records){
先頭行
db.schedule.where('name').equals('aaa').first().then (function(records){
x↓ダメ??db.schedule.where('name').equals('aaa').toArray(function(records){
alert(records.reg_date);
x↓ダメ??db.schedule.get({name: "aaa", key: "bbb"}).then (function(records){ alert (JSON.stringify(records)); for (let i in records) { alert(i + ' item has ' + records[i].reg_date); }
■Insert and select(キーのidを使う)db.schedule.add({name: "ver1.0", key: document.getElementById("inputKey").value, value: document.getElementById("inputValue").value, reg_date: getCurrentTime()}).then(function(){ db.schedule.get('2').then(function(records){ alert(JSON.stringify(records)); }).catch(function(error) { alert ("Ooops: " + error); });}).catch(function(error) { alert ("Ooops2: " + error);
■Update
putは存在があれば更新、なければ挿入
db.schedule.put({key: "bbb", reg_date: set_date}).then (function(){ return db.schedule.get('bbb');}).then(function (schedule) { alert ("Date was set at " + schedule.reg_date);
keyが出せる場合はupdate()db.friends.update(2, {name: "Number 2"}).then(function (updated) {
トランザクションや細かな変更はmodify()db.friends.where("shoeSize").aboveOrEqual(47).modify({isBigfoot: 1}); modify推奨?→ https://dexie.org/docs/Collection/Collection.modify()
■Deletedb.schedule.where({name: "aaa"}).delete().then (function(){ return db.schedule.toArray();}).then(function (records) { if(records == null || records == ''){ alert ("No data found"); }else{ alert (JSON.stringify(records)); }
■Where句
db.friends.where("shoeSize").between(40, 45).count(function(count) {
[HTML5] IndexedDBでデータの保存や読み込みを行う - Dexie.js編 (katsubemakito.net)
Dexie.jsとTypeScriptでIndexedDBを操作する - noxi雑記 (hateblo.jp)
■アクセスindexeddbは該当DBにどこからアクセスできるか>同一ドメイン、ディレクトリでじゃない保存場所C:\Users\<ユーザ>\AppData\Local\Google\Chrome\User Data\Default\IndexedDBC:\Users\<ユーザ>\AppData\Roaming\Mozilla\Firefox\Profiles\XXXXX.default\storage\default
■課題SWで外部JSを扱うにはSW内に importScripts('dexie.js'); で埋め込むSyntaxError: Unexpected token o in JSON at position 1 はオブジェクトが返っている
JSONはオブジェクトで扱うのが楽 JSON.stringify(records)とJSON.parse(records)で変換
console.log('json: ' + JSON.stringify(json)); for(i = 0; i < json.length; i++){ if(json[i] != null) { console.log('id: ' + json[i].id);
下のようなロジックはあるテーブルのSELECTループ中に他のテーブルにアクセスする入れ子なのでエラー「NotFoundError: Failed to execute 'objectStore' on 'IDBTransaction': The specified object store was not found.」→配列に入れてIndeDBの問い合わせを一旦完了し、配列のループでIndedbを操作self.addEventListener('sync', function(event){ db.que.each(function(records){ if(event.tag.startsWith('post-data:' + records.tag)){ event.waitUntil(postDataSW(db)); } }); function postDataSW(){ db.reserve.where({flg_server: 2}).toArray(function(records){DevTools failed to load SourceMap: Could not load content~のエラーが出た 効果あるか不明だがdexieの最終行のコレを削除した、文字コードがUTF8に変えたりも //# sourceMappingURL=include.prepload.js.map
■関連JS、JavascriptJSでAタグリンクを挿入するにはinsertAdjacentHTMLがよい生成したタグを追加する前に掃除するにはdocument.getElementById('xx').textContent = null;
■テストhttps://www.bangboo.com/indexeddb/indexeddb_dexie_form.htmlhttps://www.bangboo.com/indexeddb/test/indexeddb_dexie_form.html (ディレクトリ違い)
Comment (0)
■20/4/20 6:00PM
PWA
■PWA
https://digital-marketing.jp/seo/what-is-progressive-web-apps/
https://developers.google.com/web/fundamentals/architecture/app-shell?hl=ja
https://qiita.com/kimamula/items/4e25b8d2caca314f9dd2
https://html5experts.jp/osamum_ms/25709/
Service Worker, App Shell Model, PRPL Petternなどを駆使したGoogleが提唱するProgressive Web Apps
HTML / JS / CSS だけでほぼ構成、モバイルApp的なWebが提供できる
SW(JS)がブラウザとサーバの間に位置し、データを取ったり渡したりブラウザの影/裏側で動く、サーバと考えてしまうと分かり易い?
App Shell Modelで側(画面テンプレート)をプッシュしておくと早い(JSレスポンス悪すぎるからコレってな)
PRPLは以下の略
Push: 最初のURLルートに不可欠なリソースを Pushする。
Render: 最初のルートを Renderする。
Pre-cache: 残りのルートをPre-cache(事前キャッシュ)する。
Lazy-load: オンデマンドで残りのルートをLazy-load(遅延読み込み)する。
/// Service Worker
訪問がない状態でのバックグラウンド同期、プッシュメッセージ
データの更新を集中的に受信して複数ページがデータの一部を利用(ブラウザにインストールするプロキシ、コンテンツの管理やキャッシュ管理を行える)
https://developer.mozilla.org/ja/docs/Web/API/Service_Worker_API
https://qiita.com/kei4eva4/items/fa5f99211e45b7ca6f6e
→正確にはバックグラウンド同期では無い>periodic background syncはユーザに粘着可で無くなりそう
Periodic Background Sync 及び Web を Install するということ | blog.jxck.io
スコープへアクセスがあった場合にキャッシュの更新はできる、あるいは
syncをオフ時に仕込んでおけばオンライン時にバックグラウンド同期は可能
beacon apiを実行させると離脱時にサーバに通信も可能
Webページとは別にバックグランドで実行するスクリプト(マルチスレッドになると思う)
ブラウザでキャッシュ操作ができる、容量もあり強力、データ期限や破棄に注意
DOMに直接アクセスできない
ブラウザを開いていなくても動作可能
プログラム可能なネットワークプロキシとして動作可能
HTTPS必須(またはlocalhost)
ネットワークに依存しない、早い(阿部寛はもっと早い)、プッシュ通信、ホームに追加
/// プッシュ配信
https://liskul.com/push-notification-25520
https://www.fenrir-inc.com/jp/boltzengine/
https://pushnate.com/labs/pushnate/webpush-case-guide
事前のコンセントがなくいきなり「プッシュ通知を有効にしますか」と出てくるのは駄目
ユーザーに何の通知をするかを知らせたい
通知から訪れたときの表示と挙動を決めておく
セグメントを分けて送りたい場合はセグメントの情報をどうやってブラウザからたどってくるか
スマホではアプリのインストールが必要だが、Webプッシュはブラウザさえあれば通知ができる
https://webtan.impress.co.jp/e/2018/12/06/31214
通知の許可のダイアログを出す前にダイアログで説明
https://push.appirits.com/webpush/
過去評価の高かったボケが、1日1つプッシュ通知で配信
1日1回、時間帯としては「20時前後」に4回に分けて(通常19:50、20:00、20:10、20:20)配信
年末年始やGWだけは、昼と夜に(1日2回)
DAUに15〜20%くらい影響がでます。「プッシュしないとアプリを開かない人」が一定数いる
アクティブ度、アプリのバージョン、端末のOS、属性(男女)などで、セグメント配信
休眠ユーザーだけにプッシュ1万人に1人くらい(0.01%)しか起動せず難しい
https://appmarketinglabo.net/bokete-push/
配信方法は現在ではChromはGCMを、FirefoxはSimplePushサーバを利用
受信する端末ごとに異なるメッセージを送るにはGoogle Firebaseが使われる様である
https://android.googleapis.com/gcm/send https://updates.push.services.mozilla.com/push/
その許可を得たプッシュのエンドポイントを使用し配信
GCMの場合1度の送信で1000デバイスまで同時に指定できる、FirefoxのSimplePushでは1デバイスでの送信
デスクトップ/Webの場合はデスクトップに通知される(Win10の場合?Win7でも通知が来る)
https://qiita.com/zaru/items/f6e821052abb1b18bb0b
https://techblog.asahi-net.co.jp/entry/2018/09/28/173705
ここにブラウザのベンダーの提供サービスを通じてとある(モバイルはコレしか)
サーバーからブラウザを通じてデスクトップ通知する方法(Push API を利用) – ラボラジアン (laboradian.com)
/// 結局
キャッシュを弄くってオフラインでもゴニョゴニョできる、先読みも可、プッシュ通知も可、モバイルAppぽく
→G suiteのOfficeアプリがオフラインでもサクサク、メール受信通知もできるGoogleのG suite用みたいなもんか
→PCよりスマホを売りたい、通信量でも儲けたいfbgcp、個人情報を取りたいので、モバイル優先、モバイルっぽくWeb、これはi-modeやね
UR evil.
■どのようにPWA化するのか
https://techblog.asahi-net.co.jp/entry/2018/07/06/145931
https://techblog.asahi-net.co.jp/entry/2018/08/10/175407
1)Service Worker(PWAで必要な処理を記述するJavaScriptファイル)
2)Manifest(PWAの仕様について記述するJSONファイル)
3)アイコン…ショートカットアイコン、プッシュ通知時のアイコンや起動画面で配置する画像
navigator.serviceWorker.register('/servicew.js').then JSファイルを登録(中に下記を入れ込む)
//キャッシュ動作部分
├addEventListener('install' キャッシュのインストール
├addEventListener('activate' アクティベート
└addEventListener('fetch' オフラインでもアクセスの際にクライアントが呼んでキャッシュ取得
//通知部分
├addEventListener('push で通知を受ける
└addEventListener("notificationclick で通知クリック時の挙動
Notification.requestPermission(function(status) { 通知許可は登録JS外でOK
キャッシュはリンク先も保持する?、、、→せえへんけど?
ブラウザのURLの鍵アイコンをクリックすると通知許可を含めて設定ができる(デフォルトの確認、許可、ブロック)
デフォルトとブロックの時は「Push通知On」のボタンを表示 → ボタンを押すとダイアログで何が通知されるか表示しOKボタンを押せる
許可状態のときは「Push通知Off」のアイコンを表示
キャッシュ優先のsw.jsだと更新ボタンでもキャッシュのまま、Cookieの中のサービスワーカーを削除
Service Workerキャッシュ戦略>保存日はindexedbに置き、HTMLの有効期限は1日にする
※参考まで、なおindexeddbは永続データで大きくなりすぎたら使用に応じて自動的に削られる仕様だったかと
https://qiita.com/tiwu_official/items/47e8a7c3e6f2d57816d7
https://techblog.asahi-net.co.jp/entry/2018/09/28/173705
受信する端末ごとに異なるメッセージを送るにはGoogle Firebaseが使われる様である
トークンを発行し端末とトークンを紐付け配信サーバに記録
配信サーバでメッセージを作り、個別トークンに対しメッセージ通知
JSONを送るがトークンを持っているもののみ通知が発火?
Notifications APIを見てみよう
https://developer.mozilla.org/ja/docs/WebAPI/Using_Web_Notifications
https://developer.mozilla.org/ja/docs/Web/API/Push_API
https://developer.mozilla.org/ja/docs/Web/API/ServiceWorkerRegistration/pushManager
モバイルでなくPCへの通知自体はServiceWorkerが不要、下記2ステップでOK(ブラウザに許可を入れるのが必要)
Notification.requestPermission(function(status) { 許可を取り
var n = new Notification(theTitle,options); 通知を送る
カレンダーをサブカレンダー(o)化しPWAでオフライン対応(機内モード)
キャッシュするよう指定しても現アクセス分の有効時間のキャッシュの影響か直ぐにはオフライン化はしないようだ(反映に~30分掛かることがある)
一度オフライン時にアクセスしエラーが出た上、ネットワークが回復すると自動にキャッシュされる
エラーを出さずに一度通常表示をするだけではキャッシュされなかった
アンドオイドでは通知も来る(1:準備ができるとChromeでページが読み込まれます→2:ページを表示できます)
ServiceWorker内ではLocalStorageはセキュリティを理由らしいが使えない
IndexedDBは使えるらしいが、、、→使えんかった、よく分らん→ラッパー要るやろでOK
SWをインスコしたディレクトリへのアクセスでSWが動く、SWJS自体へのアクセスではない
PHPでJSを吐いてもOK js_serviceworker.phpとかでheaderをJavascriptとして
https://webtan.impress.co.jp/e/2019/08/26/33636
JavaScriptを使えば、ページをリロードせずにブラウザのURLバーの内容を変更できるのでSPAでも戻るが使える
history.pushState(null, "Page 2", "/page2.html");
https://webtan.impress.co.jp/e/2019/09/02/33637
Fetch APIを使用してリクエストをハイジャックしたりレスポンスを変更または改ざん出来る
https://qiita.com/propella/items/6500f76c9c1521878a6b
https://developers.google.com/web/fundamentals/primers/service-workers?hl=ja
SWが分かりやすい
ServiceWorker, Cache API を使用して 4万件のアセット永続化を試した話 | 株式会社ノックノート (knocknote.co.jp)
キャッシュを解析してどれ位まで保持してるか、URL等々も確認もできる
if(status ==200 && ok){ //okはtrue/falseが入るが文字にはなれずboolean
キャッシュ削除もできるが、Indexeddbで管理をするし、SW1発目ではインスコだけで動作しない等で扱いは手間
Sync(まだ非標準)
通信がオフからオンに変わった時点で発火。バックグラウンドで何かを実施する、indexeddbにデータを貯めオンでサーバに送る等を行う
https://qiita.com/horo/items/28bc624b8a26ffa09621
Periodic background syncはメールクライアントの受信ボックス更新のように、バックグラウンドで定期的にタスクを実行
ユーザのIPを定期的に確認しトラックできる安全性、バッテリやリソースやギガ消費などの問題がある
AddtoHomeやサイトエンゲージメントが高いや普段使いのネットワーク等で発火が判断される
https://blog.jxck.io/entries/2020-04-23/periodic-background-sync.html
WORKBOX(google)のsyncは仕様が違う?分からん
エクスポネンシャルバックオフ(補足: 最大再試行回数に達するまで、指数関数的に増加する待機時間で操作を再試行する手法 – 1秒、2秒、4秒、8秒、16秒...とシステムに負担をかけない)を採用
syncイベントはユーザがアプリケーションから離れた時にも送られます
テスト時はインターネットを遮断するか(オフライン)、ウェブサーバーを落としてください。Chrome DevToolのOfflineモードは使用しないでください!Devtoolのofflineチェックボックスはページからのリクエストのみに影響があります。サービスワーカーのリクエストは成功してしまいます
/// 開発上の注意
FetchAPIは404や500エラーをthenで受け取れない
JS送り側>サーバ側>JS受け と丁寧に開発を
SWからDOMは弄れない→sync全体描画やpostMessageやnew Responseの検討?
JSONの値はダブルコーテーションで括る必要がある
header("Content-type: application/json; charset=UTF-8");
echo '{ "response": "no data" }';
JSONでレコード数がある場合は[]で括る必要がある
echo '[{ "response": "1" },{ "response": "2" }';
PHPではPOSTデータを$_POSTで受け取れるのは「application/x-www-form-urlencoded」又は「multipart/form-data」のみ
file_get_contents('php://input')で受け取る
$contents = json_decode(file_get_contents('php://input'), true);
モバイルだとhttp://でアクセスしているかどうか分かりにくいが、https://でしか動作しない
/// mmm
キャッシュが強い(失敗すれば手動でCookie/Serviceworker削除、くせ強でユーザに古いページを見せ続けるかも)
どの範囲をキャッシュするか、キャッシュページの案内方法(お気に入りに入れる?)をどうするか
SinglePageAppなら枠だけキャッシュさせて後は外部JSでいけるが
工夫を考える:転送、外部JS、IFRAME、AJAX、サーバサイド、Refer、new Response等々
データもキャッシュする場合は2パターンかな、Pros cons、データをIndexeddbに持つ形ならモバイルApp的SinglePageApp的
┏online
┃1)DLしたキャッシュを表示(最新情報が届かない、だが何をキャッシュしているか分かる、サブサイト化でバックアップ用途)
┃ 普段使わないとキャッシュ更新を忘れる、SWがDL中のものを反映し表示するのに時間が掛かる(時々転送やIFRAME系でDL?)
┃2)最新を表示、裏でDLを実施(最新を表示しているので通常利用できる、DL頻度を下げたりProxy/キャッシュで動作が分からないところがある?)
┃ 何をキャッシュしているか不明、(バックアップサイトを持たず一体で便利だが、頻繁にキャッシュ全更新しがち、動作不明なことがある)
┗offline
キャッシュを表示
Notification API(デスクトップ通知)
デスクトップ通知スケジューラー (bangboo.com)
Cache API(スケジュールのオフライン化)
https://pimpub.jpn.org/aaaaaa/o/install.php
Service worker API - Sync(バックグラウンド同期)
オフライン予約システム (bangboo.com)
Comment (0)
Navi: < 9 | 10 | 11 | 12 >
-Home
-Column [128]
-Europe [9]
-Gadget [77]
-Web [133]
-Bike [4]
@/// BANGBOO BLOG ///