Using composition in JavaScript - javascript

I want to make a request and cache it, in a functional style.
const req = (uri) =>
(console.log(`requesting: ${uri}`), Promise.resolve({ status: 200 }));
const cache = (fn) => (...args) =>
fn(...args).then((result) => { console.log('caching:', result) });
const cachedReq = cache(req);
cachedReq('example.com/foo');
Two questions:
Is this code idiomatic?
How can I supply logic to generate the cache key from the result, while maintaining separation of concerns? For example, I might use req to retrieve different kinds of resource which need different logic to generate the key to be used in the cache. How should I supply this key-generation logic to the cache function?
Edit:
In reality, the URI should be the key (thanks to #epascarello). I chose a poor example. But I'd like to ask about the more general case, where logic needs to be supplied "down composition", while maintaining decent separation of concerns.

You almost close to achieve your goal, you are in the right direction, with composition concept. maybe this code can help you to make your goal come true.
Let's simulate your req function like so:
var req = (uri) => {
console.log("inside req", uri);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ status: 200 });
}, 3000);
});
}
then you have the cacheFunc version as:
var withCache = (promiseFunc) => {
const cache = {};
return (...args) => {
// suppose first param is uri
var uri = args[0];
return new Promise((resolve, reject) => {
if (cache.hasOwnProperty(uri)) {
return resolve(cache[uri]);
}
promiseFunc(...args).then((data) => {
cache[uri] = data;
resolve(data);
}).catch(reject);
});
}
}
as you can see, you need to create and cache object into the first function, so this is a a little similar to Currying in JS, so you need to wrap your req (that is a promise) wrapped into another promise from the cache version, so before execute the req function, you need to verify if some response exists into cache with the same uri key, if it is, so resolve inmmediatly the promise, else execute the req function, once you receive the response cache the response and resolve the cache promise version.
So you can use it like so:
var cacheReq = withCache(req);
cacheReq('https://anywhere.com').then(console.log.bind(null, 'response')).catch(console.log.bind(null, 'error response'));
you will notice that in the first time you promise wait until 3 seconds to resolve the req, in the second call the promise will resolve the promise ASAP because of cache, if you try with another uri it will wait 3 seconds again and will cache the response to use it the next time.
Hope it can help you.

You can use a combination of a Map and the Request constructor:
// I'll be using ramda for object equality, but any
// deepEquals checker should work.
const R = window.R;
const genRequest = ((cache, eqComparator) => {
return (url, fetchOpts={}) => {
const key = {url, fetchOpts};
const alreadyHave = [...cache.keys].find(x => eqComparator(x, key));
if (alreadyHave) return cache.get(alreadyHave);
const req = new Request(url, fetchOpts);
cache.set(key, req);
return req;
};
})(new Map(), R.equals);
const req = genRequest('http://www.google.com');
fetch(req)
.then(...)
.catch(...);
Some nice properties fall out of this:
Each request is constructed only once but can be repeatedly fetched.
No side-effects until you fetch: creating the request and fetching it are separate.
...thus, concerns are about as separated as they can be.
You could re-jigger parameter application to easily support custom equality comparisons using the same cache.
You can use the same strategy to cache the results of a fetch, separately from caching the requests.

Related

Leaving jQuery, wrote a simple ajax function, but chained methods will not wait

Update: Added a simpler demonstration jsfiddle, https://jsfiddle.net/47sfj3Lv/3/.
reproducing the problem in much less code I'm trying to move away from jQuery.
Some of my code, for populating some tables, has code like this
var hb = new hbLister(url: '#attributes.listURL#')
.getData(#url.page#, #url.per#)
.search(searchColumn, searchParam)
.render();
hbLister would initialize some things
getData would perform an $.ajax call
search wouldconsole.log('filtering data') and apply the search conditions against a javascript object
render would put the results on the page.
Importantly, search wouldn't fire until after the ajax call in getData finished.
So, now I have this ajax constructor. I've abbreviated as much of this code as I can.
let ajax = function (options, hooks, headers) {
let that = this;
// enforce parameter types
// copy parameters to this.base
this.base = { options: options, hooks: hooks, headers: headers }
return function (url, options, data, hooks, headers) {
// enforce variable types
// merge options and hooks with their base.counterparts
headers = new Headers(Object.assign({}, that.base.headers, headers));
options.headers = headers;
return fetch(url, options)
.then(response => {
return response.json().then(json => {
console.log('processing');
if (response.ok) {
// it's omitted here but the Base functions are defined
// in the constructor parameters
hooks.successBase(json, response.status, response.headers);
hooks.success(response.json, response.status, response.headers)
} else {
hooks.failureBase(json, response.status, response.headers);
hooks.failure(response.json, response.status, response.headers)
}
})
});
}
}
The idea is that I can say
let tableLister = new ajax()
And thengetData can call
tableLister = tableLister(hb.url, // url
{ type: "GET" }, // options
config.data, // data
{ success: successFn } // hooks, success being the callback
)
The jQuery call would properly give me and then processing and then filtering data.
This function gives me filtering data, an error, and thenprocessing, because I cannot seem to get the chain(.search(...).render()) to wait til the ajax call is done.
Here's a self-contained example on jsFiddle, https://jsfiddle.net/47sfj3Lv/3/
I am sure the answer is in async and await, but I have not been able to get it to work.
Here is an example of what I've tried
return await (async function () {
console.log('fetching')
let fetcher = await fetch(url, options);
console.log('getting json');
return await fetcher.json().then((json) => {
console.log('have json, doing hooks');
if (fetcher.ok) {
let argXHR = { json: json}
hooks.successBase(argXHR, hooks.params);
hooks.success.forEach(v => v(argXHR, hooks.params));
hooks.afterSend(argXHR, hooks.params);
} else {
let argXHR = { json: json,}
hooks.failureBase(argXHR, hooks.params);
hooks.failure.forEach(v => v(argXHR, hooks.params));
hooks.afterError(argXHR, hooks.params);
}
console.log('finished hooks')
})
}())
And no matter what I do, the chain, continues before this await finishes..
I got code with XMLHttpRequest to work. The method chain (.getData().search(...).render()) works with this, because this doesn't allow the ajax function to return before the request is finished and callbacks are executed. **I'd still prefer to make .fetch() work.
let xhr = new XMLHttpRequest()
let urlParams = [];
Object.keys(data).forEach((v) => urlParams.push(v + '=' + encodeURIComponent(data[v])));
urlParams = urlParams.join('&')
xhr.open(options.method, options.url, false);
xhr.onreadystatechange = function(state) {
if (this.readyState == 4) {
let json = JSON.parse(xhr.responseText)
hooks.successBase(json, xhr.status, xhr.getResponseHeader);
hooks.success.forEach(v => v(json, xhr.status, xhr.getResponseHeader));
}
}
xhr.onerror = function() {
let json = JSON.parse(xhr.responseText)
hooks.failureBase(json, xhr.status, xhr.getResponseHeader);
hooks.failure.forEach(v => v(json, xhr.status, xhr.getResponseHeader));
}
for (h in headers) {
xhr.setRequestHeader(h, headers[h])
}
xhr.send(urlParams)
This was difficult for me to understand, so I wanted to share if anyone else has the same issue.
It seems that an async method will break a method chain, there's no way around that. And since fetch is asynchronous, await must be used, and in order for await to be used, the calling method must be declared async. Thus the method chain will be broken.
The way the method chain is called must be changed.
In my OP, I linked https://jsfiddle.net/47sfj3Lv/3/ as a much simpler version of the same problem. StackOverflow's 'fiddle' effectively blocks 'fetch' for security reasons, so I need to use JSFiddle for demonstration.
Here's a working version of the same code using then and how/why it works, and a slightly shorter version, because await can be specified with the the fetch, obviously.
let obj = {};
// methods with fetch ideally should be specified async.
// async calls will automatically return a promise
obj.start = async () => {
console.log('start begins')
let retText = "",
fetcher = fetch('/', {}).then(response => response.text())
.then(text => {
console.log('fetch\'s last then')
retText = text
})
// fetcher has just been declared. It hasn't done anything yet.
console.log('fetch requested, returned length:', retText.length)
// this makes the fetcher and sequential then's happen
await fetcher;
console.log('await let fetch finish, returned length:', retText.length)
// Async functions will return a promise, but the return specified here
// will be passed to the next 'then'
return obj
}
obj.middle = () => {
console.log('middle called')
// Because this is not declared async, it behaves normally
return obj;
}
obj.end = () => {
console.log('end called')
// Because this is not declared async, it behaves normally
}
console.log('Method 1: ', obj.start()
// Because start is Async, it returns obj wrapped in a promise.
// As with any function, step may be named Anything you like
.then((step) => step.middle())
// Middle is not Async, but since it's wrapped in then
// it returns a promise
.then((step) => step.end())
)
// This is just wrapped in a timer so the two logs don't intermix with each other
// This is definitely the preferred method. Non-async chain-methods that return
// a reference to their object, do not need to wrapped in then().
setTimeout(function() {
console.log('------------------------')
console.log('Method 2: ', obj.start()
// Because start is Async, it returns obj wrapped in a promise.
// As with any function, step may be named Anything you like
.then((step) => step.middle().end())
)
}, 3000)

Can't promisify callback based function

I want to use the library astro-js where a typical call in their docs looks like this:
const aztroJs = require("aztro-js");
//Get all horoscope i.e. today's, yesterday's and tomorrow's horoscope
aztroJs.getAllHoroscope(sign, function(res) {
console.log(res);
});
For several reasons, I would like to use it using async/await style and leverage try/catch. So I tried promisify like this:
const aztroJs = require("aztro-js");
const {promisify} = require('util');
const getAllHoroscopeAsync = promisify(aztroJs.getAllHoroscope);
async function handle() {
let result, sign = 'libra';
try {
result = await getAllHoroscopeAsync(sign);
}
catch (err) {
console.log(err);
}
console.log("Result: " + result);
}
However, when I log result it comes as undefined. I know the call worked since the library is automatically logging a response via console.log and I see a proper response in the logs.
How can I "await" on this call? (even by other means if this one is not "promisifyable")
util.promisify() expects the callback function to accept two arguments, the first is an error that must be null when there is no error and non-null when there is an error and the second is the value (if no error). It will only properly promisify a function if the callback follows that specific rule.
To work around that, you will have to manually promisify your function.
// manually promisify
aztroJs.getAllHoroscopePromise = function(sign) {
return new Promise(resolve => {
aztroJs.getAllHoroscope(sign, function(data) {
resolve(data);
});
});
};
// usage
aztroJs.getAllHoroscopePromise(sign).then(results => {
console.log(results);
});
Note, it's unusual for an asynchronous function that returns data not to have a means of returning errors so the aztroJs.getAllHoroscope() interface seems a little suspect in that regard.
In fact, if you look at the code for this function, you can see that it is making a network request using the request() library and then trying to throw in the async callback when errors. That's a completely flawed design since you (as the caller) can't catch exceptions thrown asynchronously. So, this package has no reasonable way of communicating back errors. It is designed poorly.
Try custom promisified function
aztroJs.getAllHoroscope[util.promisify.custom] = (sign) => {
return new Promise((resolve, reject) => {
aztroJs.getAllHoroscope(sign, resolve);
});
};
const getAllHoroscopeAsync = util.promisify(aztroJs.getAllHoroscope);
You could change your getAllHoroscopeAsync to a promise function
Example:
const getAllHoroscopeAsync = (sign) =>
new Promise(resolve =>
aztroJs.getAllHoroscope(sign, (res) => resolve(res)));

Tracking state of a chain of promises

I'm currently trying to track the progress of a chain of native es6 promises, and am wondering what the 'correct' way to go about this is.
I've simplified the actual code to thie following example, which is a basic set of chained promises (in reality, the promise chain is longer, and the session status value changes in more places depending on progress through the chain):
let sessions = {}
const asyncFunc = () => {
// Get a new id for state tracking
let session_id = getID()
sessions.session_id = 'PENDING'
// Fetch the first url
let result = api.get(url)
.then(res => {
// Return the 'url' property of the fetched data
return res.url
})
.then (url => {
// Fetch this second url
let data = api.get(url)
sessions.session_id = 'SUCCESS'
// Return the whole data object
return data
})
.catch(err => {
console.log("ERR", err)
sessions.session_id = 'ERROR'
})
return result
}
asyncFunc()
.then(res => {
console.log("URL", url)
})
This code tracks the state of the functions and stores them to the global sessions object - but the session_id isn't being passed back for inspection of status while the function is 'in-flight'.
One option I'm considering is adding the session_id as a property of the promise when it is returned, so this can be inspected - however I'm not sure if adding a property to a promise is a risky/hacky thing to do? Something like (simplified from above):
const asyncFunc = () => {
// Get a new id for state tracking
let session_id = getID()
sessions.session_id = 'PENDING'
// Fetch the first url
let result = api.get(url)
.then(...)
.then(...)
.catch(...)
// Add the session_id to the promise
result.session_id = session_id
return result
}
let func = asyncFunc()
let status =sessions[func.session_id]
func.then(...)
Any thoughts on the validity of this approach? I can see that I would probably also need to push the session id into the final return value as well, (so that the property exists in both the promise, and the resulting value of the resolved/rejected promise).
Alternatively, any other ways of handling this?
The obvious one is to make the function always return an array of arguments (promise and session_id) but I'd prefer to avoid having to always do e.g.:
let func = asyncFunc()
let status =sessions[func[1]]
func[0].then(...)

wait on a promise for each iteration in nodejs

I am developing a nodejs application that needs to get settings from an array(in a settings object), call a rest api based on the settings and write the response to mongodb and repeat this for the next setting in the array.
Here is a simplified version of the application
var setting //global
process(){ //top level function
for(let s of config.settings){
setting = s;
getData();
}
}
function getData(){
init()
.then(makeRequest) // constructs and makes the rest api call
.then(insert) // writes the response to the db
.catch(function(err){
// logs err
}
}
Running it, only the data for the last setting (in the array) is written to the db and this happens for each iteration. Basically the same data is written on the db for as many iterations.
The problem I can see from this is that the for loop finishes executing, before the promises return with the value.
I have seen some examples of async.for
Any suggestions on fixing this. How do you go about designing this kind of a flow?
You can bind the settings to each function call to preserve the value. looks like you'd have to refactor though as the value would be passed in as an argument though i'm not sure if your code is pseudo code or actual code.
async await would work as well but would take longer as it would pause execution at each api call.
You should return an object or array that you can use to store an internal state for your request. Please see the example for how it works.
Also never set a global variable to store your state, with your function being asynchronous the value may not be what you expect it to be.
With this approach you are passing { init } for the first promise, then { init, request } for the next so you have the response from each part of your promise chain that you can use to make further requests.
// return an object to store the state on init
const init = () =>
new Promise((res, rej) => res({
init: 'initted'
}))
// pass init and the request to the next function in the chain
const makeRequest = ({ init }) =>
new Promise((res, rej) => res({
init,
request: {
msg: 'this is the response',
id: 33
}
}))
// insert stuff from the request
// then return the data to the next query
const insert = ({ init, request }) =>
new Promise((res, rej) => res({
request,
init,
created_at: Date.now()
}))
const trace = name => x => (console.log(name, x), x)
function getData(){
return init() // return your promise so you can chain it further
.then(trace('after init'))
.then(makeRequest)
.then(trace('after request'))
.then(insert)
.then(trace('after insert'))
.catch(console.error)
}
// call you function
getData()
// since the promise is returned we can continue the chain
.then(state => console.log({ state }))
<script src="https://codepen.io/synthet1c/pen/KyQQmL.js"></script>
All of your loop will have executed by the time the callbacks are coming in. So settings will be the last value.
Instead of relying on globals, pass setting into getData, for example.

JS Promise - instantly retrieve some data from a function that returns a Promise

Can anyone recommend a pattern for instantly retrieving data from a function that returns a Promise?
My (simplified) example is an AJAX preloader:
loadPage("index.html").then(displayPage);
If this is downloading a large page, I want to be able to check what's happening and perhaps cancel the process with an XHR abort() at a later stage.
My loadPage function used to (before Promises) return an id that let me do this later:
var loadPageId = loadPage("index.html",displayPage);
...
doSomething(loadPageId);
cancelLoadPage(loadPageId);
In my new Promise based version, I'd imagine that cancelLoadPage() would reject() the original loadPage() Promise.
I've considered a few options all of which I don't like. Is there a generally accepted method to achieve this?
Okay, let's address your bounty note first.
[Hopefully I'll be able to grant the points to someone who says more than "Don't use promises"... ]
Sorry, but the answer here is: "Don't use promises". ES6 Promises have three possible states (to you as a user): Pending, Resolved and Rejected (names may be slightly off).
There is no way for you to see "inside" of a promise to see what has been done and what hasn't - at least not with native ES6 promises. There was some limited work (in other frameworks) done on promise notifications, but those did not make it into the ES6 specification, so it would be unwise of you to use this even if you found an implementation for it.
A promise is meant to represent an asynchronous operation at some point in the future; standalone, it isn't fit for this purpose. What you want is probably more akin to an event publisher - and even that is asynchronous, not synchronous.
There is no safe way for you to synchronously get some value out of an asynchronous call, especially not in JavaScript. One of the main reasons for this is that a good API will, if it can be asynchronous, will always be asynchronous.
Consider the following example:
const promiseValue = Promise.resolve(5)
promiseValue.then((value) => console.log(value))
console.log('test')
Now, let's assume that this promise (because we know the value ahead of time) is resolved synchronously. What do you expect to see? You'd expect to see:
> 5
> test
However, what actually happens is this:
> test
> 5
This is because even though Promise.resolve() is a synchronous call that resolves an already-resolved Promise, then() will always be asynchronous; this is one of the guarantees of the specification and it is a very good guarantee because it makes code a lot easier to reason about - just imagine what would happen if you tried to mix synchronous and asynchronous promises.
This applies to all asynchronous calls, by the way: any action in JavaScript that could potentially be asynchronous will be asynchronous. As a result, there is no way for you do any kind of synchronous introspection in any API that JavaScript provides.
That's not to say you couldn't make some kind of wrapper around a request object, like this:
function makeRequest(url) {
const requestObject = new XMLHttpRequest()
const result = {
}
result.done = new Promise((resolve, reject) => {
requestObject.onreadystatechange = function() {
..
}
})
requestObject.open(url)
requestObject.send()
return requestObject
}
But this gets very messy, very quickly, and you still need to use some kind of asynchronous callback for this to work. This all falls down when you try and use Fetch. Also note that Promise cancellation is not currently a part of the spec. See here for more info on that particular bit.
TL:DR: synchronous introspection is not possible on any asynchronous operation in JavaScript and a Promise is not the way to go if you were to even attempt it. There is no way for you to synchronously display information about a request that is on-going, for example. In other languages, attempting to do this would require either blocking or a race condition.
Well. If using angular you can make use of the timeout parameter used by the $http service if you need to cancel and ongoing HTTP request.
Example in typescript:
interface ReturnObject {
cancelPromise: ng.IPromise;
httpPromise: ng.IHttpPromise;
}
#Service("moduleName", "aService")
class AService() {
constructor(private $http: ng.IHttpService
private $q: ng.IQService) { ; }
doSomethingAsynch(): ReturnObject {
var cancelPromise = this.$q.defer();
var httpPromise = this.$http.get("/blah", { timeout: cancelPromise.promise });
return { cancelPromise: cancelPromise, httpPromise: httpPromise };
}
}
#Controller("moduleName", "aController")
class AController {
constructor(aService: AService) {
var o = aService.doSomethingAsynch();
var timeout = setTimeout(() => {
o.cancelPromise.resolve();
}, 30 * 1000);
o.httpPromise.then((response) => {
clearTimeout(timeout);
// do code
}, (errorResponse) => {
// do code
});
}
}
Since this approach already returns an object with two promises the stretch to include any synchronous operation return data in that object is not far.
If you can describe what type of data you would want to return synchronously from such a method it would help to identify a pattern. Why can it not be another method that is called prior to or during your asynchronous operation?
You can kinda do this, but AFAIK it will require hacky workarounds. Note that exporting the resolve and reject methods is generally considered a promise anti-pattern (i.e. sign you shouldn't be using promises). See the bottom for something using setTimeout that may give you what you want without workarounds.
let xhrRequest = (path, data, method, success, fail) => {
const xhr = new XMLHttpRequest();
// could alternately be structured as polymorphic fns, YMMV
switch (method) {
case 'GET':
xhr.open('GET', path);
xhr.onload = () => {
if (xhr.status < 400 && xhr.status >= 200) {
success(xhr.responseText);
return null;
} else {
fail(new Error(`Server responded with a status of ${xhr.status}`));
return null;
}
};
xhr.onerror = () => {
fail(networkError);
return null;
}
xhr.send();
return null;
}
return xhr;
case 'POST':
// etc.
return xhr;
// and so on...
};
// can work with any function that can take success and fail callbacks
class CancellablePromise {
constructor (fn, ...params) {
this.promise = new Promise((res, rej) => {
this.resolve = res;
this.reject = rej;
fn(...params, this.resolve, this.reject);
return null;
});
}
};
let p = new CancellablePromise(xhrRequest, 'index.html', null, 'GET');
p.promise.then(loadPage).catch(handleError);
// times out after 2 seconds
setTimeout(() => { p.reject(new Error('timeout')) }, 2000);
// for an alternative version that simply tells the user when things
// are taking longer than expected, NOTE this can be done with vanilla
// promises:
let timeoutHandle = setTimeout(() => {
// don't use alert for real, but you get the idea
alert('Sorry its taking so long to load the page.');
}, 2000);
p.promise.then(() => clearTimeout(timeoutHandle));
Promises are beautiful. I don't think there is any reason that you can not handle this with promises. There are three ways that i can think of.
The simplest way to handle this is within the executer. If you would like to cancel the promise (like for instance because of timeout) you just define a timeout flag in the executer and turn it on with a setTimeout(_ => timeout = true, 5000) instruction and resolve or reject only if timeout is false. ie (!timeout && resolve(res) or !timeout && reject(err)) This way your promise indefinitely remains unresolved in case of a timeout and your onfulfillment and onreject functions at the then stage never gets called.
The second is very similar to the first but instead of keeping a flag you just invoke reject at the timeout with proper error description. And handle the rest at the then or catch stage.
However if you would like to carry the id of your asych operation to the sync world then you can also do it as follows;
In this case you have to promisify the async function yourself. Lets take an example. We have an async function to return the double of a number. This is the function
function doubleAsync(data,cb){
setTimeout(_ => cb(false, data*2),1000);
}
We would like to use promises. So normally we need a promisifier function which will take our async function and return another function which when run, takes our data and returns a promise. Right..? So here is the promisifier function;
function promisify(fun){
return (data) => new Promise((resolve,reject) => fun(data, (err,res) => err ? reject(err) : resolve(res)));
}
Lets se how they work together;
function promisify(fun){
return (data) => new Promise((resolve,reject) => fun(data, (err,res) => err ? reject(err) : resolve(res)));
}
function doubleAsync(data,cb){
setTimeout(_ => cb(false, data*2),1000);
}
var doubleWithPromise = promisify(doubleAsync);
doubleWithPromise(100).then(v => console.log("The asynchronously obtained result is: " + v));
So now you see our doubleWithPromise(data) function returns a promise and we chain a then stage to it and access the returned value.
But what you need is not only a promise but also the id of your asynch function. This is very simple. Your promisified function should return an object with two properties; a promise and an id. Lets see...
This time our async function will return a result randomly in 0-5 secs. We will obtain it's result.id synchronously along with the result.promise and use this id to cancel the promise if it fails to resolve within 2.5 secs. Any figure on console log Resolves in 2501 msecs or above will result nothing to happen and the promise is practically canceled.
function promisify(fun){
return function(data){
var result = {id:null, promise:null}; // template return object
result.promise = new Promise((resolve,reject) => result.id = fun(data, (err,res) => err ? reject(err) : resolve(res)));
return result;
};
}
function doubleAsync(data,cb){
var dur = ~~(Math.random()*5000); // return the double of the data within 0-5 seconds.
console.log("Resolve in " + dur + " msecs");
return setTimeout(_ => cb(false, data*2),dur);
}
var doubleWithPromise = promisify(doubleAsync),
promiseDataSet = doubleWithPromise(100);
setTimeout(_ => clearTimeout(promiseDataSet.id),2500); // give 2.5 seconds to the promise to resolve or cancel it.
promiseDataSet.promise
.then(v => console.log("The asynchronously obtained result is: " + v));
You can use fetch(), Response.body.getReader(), where when .read() is called returns a ReadableStream having a cancel method, which returns a Promise upon cancelling read of the stream.
// 58977 bytes of text, 59175 total bytes
var url = "https://gist.githubusercontent.com/anonymous/"
+ "2250b78a2ddc80a4de817bbf414b1704/raw/"
+ "4dc10dacc26045f5c48f6d74440213584202f2d2/lorem.txt";
var n = 10000;
var clicked = false;
var button = document.querySelector("button");
button.addEventListener("click", () => {clicked = true});
fetch(url)
.then(response => response.body.getReader())
.then(reader => {
var len = 0;
reader.read().then(function processData(result) {
if (result.done) {
// do stuff when `reader` is `closed`
return reader.closed.then(function() {
return "stream complete"
});
};
if (!clicked) {
len += result.value.byteLength;
}
// cancel stream if `button` clicked or
// to bytes processed is greater than 10000
if (clicked || len > n) {
return reader.cancel().then(function() {
return "read aborted at " + len + " bytes"
})
}
console.log("len:", len, "result value:", result.value);
return reader.read().then(processData)
})
.then(function(msg) {
alert(msg)
})
.catch(function(err) {
console.log("err", err)
})
});
<button>click to abort stream</button>
The method I am currently using is as follows:
var optionalReturnsObject = {};
functionThatReturnsPromise(dataToSend, optionalReturnsObject ).then(doStuffOnAsyncComplete);
console.log("Some instant data has been returned here:", optionalReturnsObject );
For me, the advantage of this is that another member of my team can use this in a simple way:
functionThatReturnsPromise(data).then(...);
And not need to worry about the returns object. An advanced user can see from the definitions what is going on.

Categories

Resources