UNPKG

safe-flow

Version:

A safer way to cancel or interrupt the await-async flow.

2 lines (1 loc) 11.6 kB
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).safeFlow={})}(this,function(e){"use strict";function t(e,t){return void 0===e?t:e}function n(e,t){return null==e?t:e}function o(e,o,r=!0){return function(e,t,n){if(!t||t===n)return n;const o=t;return Object.keys(n).forEach(t=>{o[t]=e(o[t],n[t])}),t}(r?t:n,e,o)}const r=1,a=2;const i=new class{constructor(){this.plugins=[]}onState(e,t){this.plugins.forEach(n=>{n&&n.onState(e,t)})}add(e){if(!(this.plugins.indexOf(e)<0))throw new Error("[safe-flow] This plugin has been added.");this.plugins.push(e)}remove(e){const t=this.plugins.indexOf(e);if(!(t>=0))throw new Error("[safe-flow] Cannot remove unadded plugin.");this.plugins[t]=void 0}};function s(){return new Error("[safe-flow] Please report this bug to the author.")}const c="[safe-flow] Some child threads have lost control. There should be no unfinished child threads in the completed parent thread.";let l=function(e){return e[e.pending=0]="pending",e[e.fulfilled=1]="fulfilled",e[e.rejected=2]="rejected",e[e.canceled=3]="canceled",e}({});class h{get hasChildren(){return Boolean(this.children.length)}get completed(){return this._completed}get disposed(){return this._disposed}levelup(){this._level++}constructor(e,t,n,o,r,a=!0,i,s,c,h){this.func=e,this.creator=t,this.parent=n,this.token=o,this.trace=r,this.autoBreak=a,this.name=i,this.id=s,this.onState=c,this.plugins=h,this.promiseState=l.pending,this.canceled=!1,this.state=void 0,this.canceler=void 0,this.cancellation=void 0,this.children=[],this.willCancel=!1,this.willBreak=!1,this._completed=!1,this._disposed=!1,this._level=-1,this.levelup(),C&&B.push(this)}changeState(e,...t){if(this.state=e,e===O.thread_canceled?this.promiseState=l.canceled:e===O.thread_completed?this.promiseState=l.fulfilled:e===O.thread_error&&(this.promiseState=l.rejected),this.disposed||i.onState(e,this),this.plugins&&this.plugins.forEach(t=>{t.onState(e,this)}),this.onState&&this.onState(e,this),this.trace&&this.name)if(e===O.thread_starting||e===O.thread_idle||e===O.thread_completed||e===O.thread_disposed)x(this.trace,{state:e,name:this.name,id:this.id});else if(e===O.thread_canceled)x(this.trace,{state:e,name:this.name,id:this.id,reason:t[0]});else if(e===O.thread_error)x(this.trace,{state:e,name:this.name,id:this.id,error:t[0]});else if(e===O.thread_done)x(this.trace,{state:e,name:this.name,id:this.id,value:t[0]});else{if(e!==O.thread_done_canceled)throw s();x(this.trace,{state:e,name:this.name,id:this.id,value:t[0]})}}addChild(e){this.children.push(e)}removeChild(e){this.children.splice(this.children.indexOf(e),1)}complete(e=!0){if(this.completed)throw s();this._completed=!0,this.changeState(O.thread_completed),this.parent&&!this.parent.hasChildren&&this.parent.levelup(),m=Boolean(this.parent),this.func=void 0,this.creator=void 0,this.parent=void 0,this.token=void 0,this.canceler=void 0,this.cancellation=void 0,this.onState=void 0,this.plugins=void 0,this.children.length=0,this.children=void 0,e&&this.dispose()}dispose(){if(!this.completed||this.disposed)throw s();this._disposed=!0,this.changeState(O.thread_disposed),this.trace=void 0,C&&B.splice(B.indexOf(this),1)}}class d{constructor(e,t,n,o){this.creator=e,this.thisArg=t,this.args=n,this.reason=o}retry(){return this.creator.apply(this.thisArg,this.args)}}const f={trace:!1,standalone:!1,filter:()=>!1,development:"auto"};let u=f;const _=new Map,p=new Map;let w,g=!1,m=!1;function v(e){C&&(e?e&&e.name?k.log(`[safe-flow] Change the current thread pointer to [${e.name}].`):k.log("[safe-flow] The current thread pointer has changed."):k.log("[safe-flow] Clear the current thread pointer.")),w=e}function b(){return m?w:void 0}const k={log:(...e)=>{console.log(...e)}},S=k,y=_,E=p;let C=!1;const B=[];let O=function(e){return e[e.creator_created=0]="creator_created",e[e.thread_starting=1]="thread_starting",e[e.thread_idle=2]="thread_idle",e[e.thread_canceled=3]="thread_canceled",e[e.thread_error=4]="thread_error",e[e.thread_done=5]="thread_done",e[e.thread_done_canceled=6]="thread_done_canceled",e[e.thread_completed=7]="thread_completed",e[e.thread_disposed=8]="thread_disposed",e}({});const j=e=>{const{name:t,state:n}=e;let o="none";if(e.state===O.thread_canceled)o="canceled"+(void 0===e.reason?"":` ${e.reason}`);else if(e.state===O.thread_error)o=`error ${e.error}`;else if(e.state===O.thread_done)o="done"+(e.value?` ${e.value}`:"");else if(e.state===O.thread_done_canceled)o="done (canceled)"+(e.value?` ${e.value}`:"");else switch(n){case O.thread_starting:o="start";break;case O.thread_idle:o="idle";break;case O.thread_completed:o="completed";break;case O.thread_disposed:o="disposed"}o=e.state===O.creator_created?" Creator is created.":(e.id?`(${e.id})`:"")+": "+o,k.log(`[safe-flow] [${t}]`+o)};function x(e,t){!0===e?j(t):e(t)}const T="[safe-flow] Do not use try/catch and .catch() on threads. This will cause the parent thread to fail to interrupt when it is cancelled. Use .errf() to receive exceptions instead.";function P(){return T}function $(e,t,n){const i=o(t,u);let{token:l}=i;const{trace:f,autoBreak:w,name:k,standalone:S,onState:y,plugins:E}=i;return f&&k&&(!function(e){if(e in q)throw new Error(`[safe-flow] Duplicate flow name "${e}".`);q[e]=0}(k),x(f,{name:k,state:O.creator_created})),function t(...o){if(g)throw new Error("[safe-flow] In the cancelled thread, no new child threads should be started. When autoBreak is set to false, you must manually end function execution of the cancelled thread.");const i=b(),u=f&&!S&&k?function(e){return++q[e]}(k):void 0;void 0===l&&(l=n);let C=_.get(l);if(C)if(C.includes(t)){if(S)throw new Error("[safe-flow] Standalone mode flow only allows one thread to execute for one creator.")}else C.push(t);else C=[],C.push(t),_.set(l,C);!p.has(t)&&p.set(t,[]);const B=p.get(t);if(!B)throw s();const j=new h(e,t,i,l,f,w,k,u,y,E);B.push(j),j.changeState(O.thread_starting),v(j),Promise.resolve().then(()=>{m=!1}),m=!0;let x=[];const $=new Promise((l,h)=>{j.canceler=(e,r)=>{if(j.state===O.thread_starting)throw new Error("[safe-flow] Do not cancel the thread while starting.");j.willCancel=!0,D(j,e),i&&i.removeChild(j);const{creator:a,token:c}=j,f=p.get(a),u=_.get(c);if(!f||!u)throw s();r&&F(j,f,a,u);const w=new d(t,n,o,e);j.cancellation=w,j.canceled=!0,j.changeState(O.thread_canceled,e),x.forEach(t=>{t(e)}),x.length=0,x=void 0,j.complete(!1),v(i),i&&!i.willBreak&&i.willCancel&&i.autoBreak?h(P()):l([w]),Promise.resolve().then(()=>{g=!1}),g=!0},i&&i.addChild(j),function(e){return e.then(e=>[null,e]).catch(e=>[e])}(e.call(n,...o)).then(e=>{if(j.canceled)return j.changeState(O.thread_done_canceled,e[1]),void j.dispose();if(i&&i.removeChild(j),!C)throw s();if(e.length===r){D(j,"An error occurred in the parent thread.");const[n]=e;j.changeState(O.thread_error,n),F(j,B,t,C),j.complete(),v(i),h(n)}else{if(e.length!==a)throw s();{const[,n]=e;if(j.changeState(O.thread_done,n),F(j,B,t,C),j.hasChildren)return D(j,"An error occurred in the parent thread."),j.complete(),v(i),void h(new Error(c));j.complete(),v(i),l([null,n])}}}),j.changeState(O.thread_idle),v(i),j.parent||(m=!1),g=!1});return $.safe_flow_promise=!0,$.cancel=e=>{if(j.completed)throw new Error("[safe-flow] Unable to cancel a thread that has ended.");R(j,e)},$.canceled=()=>j.canceled,$.state=()=>j.promiseState,$.onCancel=e=>(x.push(e),$),$.errf=()=>{const e=$.then(([e,t])=>null===e?[null,null,t]:[null,e]).catch(e=>{if(e===T||e instanceof Error&&e.message===c)throw e;return[e]});return e.safe_flow_promise=!0,e.cancel=$.cancel,e.canceled=$.canceled,e.state=$.state,e.onCancel=t=>(x.push(t),e),e},$}}function A(e){return"function"==typeof e&&"safe_flow_creator"===e.name}function D(e,t){const{children:n}=e;n.concat().forEach(e=>{R(e,t)})}function F(e,t,n,o){t.splice(t.indexOf(e),1),0===t.length&&(p.delete(n),o.splice(o.indexOf(n),1),0===o.length&&_.delete(e.token))}function I(e,t,n,o=!1){let r=!1;if(A(t)){const a=p.get(t);if(a){const i=a[0].token;a.forEach(t=>{if(!t.canceler)throw s();o||!e||r||(r=e.willBreak=M(t,e)),t.canceler(n,!1)}),a.length=0,p.delete(t);const c=_.get(i);if(!c)throw s();const l=c.indexOf(t);c.splice(l,1),0===c.length&&_.delete(i)}}else{const a=_.get(t);a&&(a.forEach(t=>{const a=p.get(t);if(!a)throw s();a.forEach(t=>{if(!t.canceler)throw s();o||!e||r||(r=e.willBreak=M(t,e)),t.canceler(n,!1)}),a.length=0,p.delete(t)}),_.delete(t))}if(r&&e&&e.autoBreak)throw P()}function M(e,t){if(e===t)return!0;const{children:n}=e;return n.some(e=>e===t||M(e,t))}function R(e,t){const{canceler:n}=e;if(!n)throw s();n(t,!0)}function N(e){return!0===e.__safe_flow_flowup}function U(e,t){if(N(e))throw new Error("[safe-flow] flowup can only be used on objects that have not yet flowup.");const n=o(t,u),{names:r}=n;r&&Object.keys(r).forEach(t=>{if("function"!=typeof e[t])throw new ReferenceError(`[safe-flow] The specified attribute "${t}" found through the names option is not a function.`)});const a={};Object.getOwnPropertyNames(e).forEach(e=>{a[e]=!0}),Object.getOwnPropertyNames(Object.getPrototypeOf(e)).forEach(e=>{a[e]=!0}),Object.keys(a).forEach(t=>{!function(e,t,n){if("constructor"===t)return;const r=e.__safe_flow_flowable&&e.__safe_flow_flowable[t],a=n.names?n.names[t]:void 0;if(!r&&!a){if(!n.filter(t))return;if("function"!=typeof e[t])return}const{names:i,token:c,trace:l,autoBreak:h,standalone:d,plugins:f}=n;let u;if(l&&i)if(!0===a)u=t;else{if(!a)throw s();u=a.name}const _={token:c,trace:l,autoBreak:h,name:u,standalone:d,plugins:f};e[t]=$(e[t],"object"==typeof r?o(r,_):_,e)}(e,t,n)}),e.__safe_flow_flowup=!0}function W(e,t,n,o){let r;if(r=n?n.value:e[t],"function"!=typeof r)throw new ReferenceError('[safe-flow] Cannot get the target function to be flowed. The "flowable" method decorator may not be used correctly.');e.__safe_flow_flowable||(e.__safe_flow_flowable={}),e.__safe_flow_flowable[t]=o||!0}let q={};e.Canceled=d,e.ErrfState={error:1,canceled:2,done:3},e.FlowState={canceled:1,done:2},e.PromiseState=l,e.Thread=h,e.TraceState=O,e.__debug_clear_names=function(){q={}},e.__debug_clear_threads=function(){_.clear(),p.clear(),B.forEach(e=>{e.complete()}),B.length=0,w=void 0},e.__debug_creator_processes=E,e.__debug_enable=function(e){C=e},e.__debug_get_curr_thread=()=>w,e.__debug_in_thread=function(){return m},e.__debug_live_threads=B,e.__debug_logger=S,e.__debug_token_creators=y,e.cancel=function(e,t){I(b(),e,t)},e.cancelAll=function(e){const t=b();if(t&&(t.willBreak=!0),_.forEach((n,o)=>{I(t,o,e,!0)}),t&&t.autoBreak)throw P()},e.cancelSelf=function(e){const t=b();if(!t)throw new Error("[safe-flow] The called cancelSelf method is not in the currently running thread, or the thread is cancelled when autoBreak is false. Don not call it in other asynchronous callback.");if(t.willBreak=!0,R(t,e),t.autoBreak)throw P()},e.configure=function(e){u=e?o(e,u):f},e.flow=function(e,t){if("function"!=typeof e)throw new ReferenceError("[safe-flow] The func is not a function.");return $(e,t)},e.flowable=function(e,t,n){if(!t)return(t,n,o)=>{W(t,n,o,e)};W(e,t,n)},e.flowed=function(e){return e},e.flowup=function(e,t){return U(e,t),e},e.internalFlowup=U,e.isCreator=A,e.isFlowupObject=N,e.isInvalid=function(){if(!g&&!m)throw new Error("[safe-flow] The called isInvalid method is not in the currently running thread. Don not call it in other asynchronous callback.");return!b()},e.isSafeFlowPromise=function(e){return!0===e.safe_flow_promise},e.plugins=i,Object.defineProperty(e,"__esModule",{value:!0})});