New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
added streaming support #5
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,10 @@ | ||
declare const useAbortableFetch: <T>(url: string | null, init?: RequestInit) => { | ||
data: T | null; | ||
loading: boolean; | ||
error: Error | null; | ||
abort: () => void; | ||
}; | ||
export default useAbortableFetch; | ||
interface RequestInitStreaming extends RequestInit { | ||
streaming?: boolean; | ||
} | ||
declare const useAbortableFetch: <T>(url: string | null, init?: RequestInitStreaming) => { | ||
data: T | Uint8Array | null; | ||
loading: boolean; | ||
error: Error | null; | ||
abort: () => void; | ||
}; | ||
export default useAbortableFetch; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
import{useState as n,useEffect as r,useLayoutEffect as t,useRef as o}from"react";export default function(e,a){void 0===a&&(a={});var u=n({data:null,loading:0,error:null,controller:null}),l=u[0],i=u[1],c=o(!1);return t(function(){return c.current=!0,function(){c.current=!1}},[]),r(function(){var n=new AbortController;return e&&(i(function(r){return{data:null,loading:r.loading+1,error:null,controller:n}}),function(n,r,t,o){var e=Object.assign({},r,{signal:t});fetch(n,e).then(function(n){return n.ok?Promise.resolve(n):Promise.reject({message:n.statusText,status:n.status})}).then(function(n){return n.json()}).then(function(n){o(function(r){return Object.assign({},r,{data:n,loading:r.loading-1})})}).catch(function(n){var r="AbortError"!==n.name?n:null;o(function(n){return Object.assign({},n,{error:r,loading:n.loading-1})})})}(e,a,n.signal,function(n){c.current&&i(n)})),function(){return n.abort()}},[e]),{data:l.data,loading:!!l.loading,error:l.error,abort:function(){return l.controller&&l.controller.abort()}}} | ||
import{useState as n,useEffect as t,useLayoutEffect as r,useRef as e}from"react";const o=function(){function n(){}return n.prototype.then=function(t,r){const e=new n,o=this.s;if(o){const n=1&o?t:r;if(n){try{i(e,1,n(this.v))}catch(n){i(e,2,n)}return e}return this}return this.o=function(n){try{const o=n.v;1&n.s?i(e,1,t?t(o):o):r?i(e,1,r(o)):i(e,2,o)}catch(n){i(e,2,n)}},e},n}();function i(n,t,r){if(!n.s){if(r instanceof o){if(!r.s)return void(r.o=i.bind(null,n,t));1&t&&(t=r.s),r=r.v}if(r&&r.then)return void r.then(i.bind(null,n,t),i.bind(null,n,2));n.s=t,n.v=r;const e=n.o;e&&e(n)}}function u(n){return n instanceof o&&1&n.s}const l={};!function(){function n(n){this._entry=n,this._pact=null,this._resolve=null,this._return=null,this._promise=null}function t(n){return{value:n,done:!0}}function r(n){return{value:n,done:!1}}n.prototype[Symbol.asyncIterator||(Symbol.asyncIterator=Symbol("Symbol.asyncIterator"))]=function(){return this},n.prototype._yield=function(n){return this._resolve(n&&n.then?n.then(r):r(n)),this._pact=new o},n.prototype.next=function(n){const r=this;return r._promise=new Promise(function(e){const u=r._pact;if(null===u){const n=r._entry;if(null===n)return e(r._promise);function c(n){r._resolve(n&&n.then?n.then(t):t(n)),r._pact=null,r._resolve=null}r._entry=null,r._resolve=e,n(r).then(c,function(n){if(n===l)c(r._return);else{const t=new o;r._resolve(t),r._pact=null,r._resolve=null,_resolve(t,2,n)}})}else r._pact=null,r._resolve=e,i(u,1,n)})},n.prototype.return=function(n){const r=this;return r._promise=new Promise(function(e){const o=r._pact;if(null===o)return null===r._entry?e(r._promise):(r._entry=null,e(n&&n.then?n.then(t):t(n)));r._return=n,r._resolve=e,r._pact=null,i(o,2,l)})},n.prototype.throw=function(n){const t=this;return t._promise=new Promise(function(r,e){const o=t._pact;if(null===o)return null===t._entry?r(t._promise):(t._entry=null,e(n));t._resolve=r,t._pact=null,i(o,2,n)})}}();export default function(l,c){void 0===c&&(c={});var s=n({data:null,loading:0,error:null,controller:null}),a=s[0],f=s[1],h=e(!1);return r(function(){return h.current=!0,function(){h.current=!1}},[]),t(function(){var n=new AbortController;return l&&(f(function(t){return{data:null,loading:t.loading+1,error:null,controller:n}}),function(t,r,e,l){var c=Object.assign({},r,{signal:n.signal});fetch(t,c).then(function(n){return n.ok?Promise.resolve(n):Promise.reject({message:n.statusText,status:n.status})}).then(function(n){if(!r.streaming)return n.json();if(!n.body)throw new Error("Invalid response body");var t=n.body.getReader();!function(){try{var n=!1,r=function(n,t,r){for(var e;;){var l=n();if(u(l)&&(l=l.v),!l)return c;if(l.then){e=0;break}var c=r();if(c&&c.then){if(!u(c)){e=1;break}c=c.s}if(t){var s=t();if(s&&s.then&&!u(s)){e=2;break}}}var a=new o,f=i.bind(null,a,2);return(0===e?l.then(v):1===e?c.then(h):s.then(d)).then(void 0,f),a;function h(e){c=e;do{if(t&&(s=t())&&s.then&&!u(s))return void s.then(d).then(void 0,f);if(!(l=n())||u(l)&&!l.v)return void i(a,1,c);if(l.then)return void l.then(v).then(void 0,f);u(c=r())&&(c=c.v)}while(!c||!c.then);c.then(h).then(void 0,f)}function v(n){n?(c=r())&&c.then?c.then(h).then(void 0,f):h(c):i(a,1,c)}function d(){(l=n())?l.then?l.then(v).then(void 0,f):v(l):i(a,1,c)}}(function(){return!n},void 0,function(){return Promise.resolve(t.read()).then(function(t){var r=t.value;t.done?n=!0:l(function(n){return Object.assign({},n,{data:r})})})});Promise.resolve(r&&r.then?r.then(function(){}):void 0)}catch(n){return Promise.reject(n)}}()}).then(function(n){l(function(t){return Object.assign({},t,{data:n,loading:t.loading-1})})}).catch(function(n){var t="AbortError"!==n.name?n:null;l(function(n){return Object.assign({},n,{error:t,loading:n.loading-1})})})}(l,c,0,function(n){h.current&&f(n)})),function(){return n.abort()}},[l]),{data:a.data,loading:!!a.loading,error:a.error,abort:function(){return a.controller&&a.controller.abort()}}} | ||
//# sourceMappingURL=index.mjs.map |
Large diffs are not rendered by default.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Large diffs are not rendered by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The streaming option is not used until the response handling. Can't the hook detect a streaming response based on the HTTP response headers? I was under the impression that
Transfer-Encoding: chunked
together with acontent-type
that supports streaming would indicate this.Note: The code now incorrectly assumes that the response is JSON. I was already planning to add a header check for JSON and return
rsp.text()
if it isn't JSON data.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I think this is actually a better way so we don't need the
stream
option. Will play around with it to see what other indicators can be used to infer if streaming or not. And yes for json, we can just check the content-type.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was searching for some but can't really find them. I would say that structured formats with a clear start and end like JSON, XML should not be streamed, even with
Transfer-Encoding: chunked
as a partial file is invalid. Formats like text, YAML, binary could be streamed. But I guess even then the client has to know when the last chunk has arrived and the response is complete.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, streaming is dictated by the server. But even then, there's actually no reliable way to detect it atm as
Transfer-Encoding
header isn't even available on cors request. See https://developers.google.com/web/updates/2015/03/introduction-to-fetch#response_types.If somehow you have access to the server, you can expose the
Transfer-Encoding
header via https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Access-Control-Expose-Headers but that's still unreliable since you're talking to different service, and don't even have access to it to be able to expose some headers.We can't even call
.text()
or.json()
even ifcontent-type
says so because it could also be text or json but still streaming in which case.text()
or.json()
will never resolve. A more reliable way is to just return the body promise and let the caller decide what they want. Then if streaming flag is passed, we do our chunk reading and return collected chunks as they get aggregated. Thoughts?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems strange if a server allows for CORS calls, set the
Transfer-Encoding: chunked
header but then doesn't set theAccess-Control-Expose-Headers
for the client to use. But then strange things happen 😞I would suggest using streaming makes sense if the client can read the
Transfer-Encoding: chunked
header and theContent-Type
makes sense for streaming. If not use the.json()
or.text()
depending on theContent-Type
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So just to be clear, we can't use both
Transfer-Encoding
andContent-Type
to decide whether to stream or not. First,Transfer-Encoding
isn't exposed to us and second,Content-Type
has nothing to do with streaming or not since it could betext/plain
but still streaming so we can't rely on that either.But we could do:
{ streaming: true }
- return aggregated chunks as ArrayBufferrsp.body
promise and let caller decide to call.json()
or.text()
or however they want to handle the data