Deferred chains in jquery - javascript

I need to make 3 requests in chain. So for this I use jquery deffered.
Request 1
-> on done if response contains expected result then Request 2 else return empty array/null
-> on done if response contains expected result then Request 3 else return empty array/null
private request1() {
const vm = this;
vm.isLoading(true);
let deffer = system.defer(dfd => {dataService.getResponse1()
.done((response) => {
request2(response.collection))
dfd.resolve();
});
return deffer.promise();
}
private request2(collection) {
dataService.getResponse2(collection)
.done((response) => request3(response.collection));
}
private request3(collection) {
dataService.getResponse3(collection)
.done((response) => saveResponse(response.collection));
}
private saveResponse(collection) {
//do some stuff
}
in Constructor I call request1 like
vm.request1().done(() => {
vm.isLoading(false);
});
The problem is that isLoading is setted to false before saveResponse is called. How should I correctly refactor my requests-structure to update isLoading after all requests are finished?
Thanks.

Try this way (please check comments in the code):
// Request 1 -> on done Request 2 -> on done -> Request 3
private request1() {
const vm = this;
vm.isLoading(true);
let deffer = system.defer(dfd => {dataService.getResponse1()
.done((response) => {
// 1. Here you have to resolve the dfd promise after
// request2 promise is resolved. For this reason,
// I added the call to "done()" method.
request2(response.collection)).done((response2) => { dfd.resolve()});
});
return deffer.promise();
}
private request2(collection) {
// 2. You need to return the promise returned by getResponse2
return dataService.getResponse2(collection)
.done((response) => request3(response.collection));
}
private request3(collection) {
// 3. You need to return the promise returned by getResponse3
return dataService.getResponse3(collection)
.done((response) => saveResponse(response.collection));
}
private saveResponse(collection) {
//do some stuff
}
So, in request3() you return the promise returned by getResponse3() which, in turn, return the promise returned by saveResponse() called inside the done() method.
In request2() you return the promise returned by getResponse() which, in turn, returns the promise returned by request3() described in the previous paragraph.
In request1(), in the main done() callback, you call request2() and wait (using done()) it finishes before resolve the main promise.
In this way, vm.isLoading(false) should be called when request2 and request3 have been completed.

Short answer: move vm.isLoading(true); inside the body of request3, after you call saveResponse.

Related

javascript promise chain with fetch not in sequence

I want to dynamically build a promise chain, that should do things in the background. Mainly it should do some output on the web page.
That works until I put promises from fetch into the chain. Those promises are not executed in the expected sequence.
The following example shows how the chain is build:
var chain = Promise.resolve();
for(var i = 0; i < actions.length; ++i)
chain = actions[i].extendChain(chain);
function actionExample(chain) {
return chain.then(...);
}
That works with direct output:
function actionOutput(chain) {
return chain.then(new Promise(resolve => {
print('text');
resolve();
}));
}
But fetch or is not in sequence:
function actionLoad(chain) {
const url = '...';
return chain.then(new Promise(() => print('run action load\n')))
.then(() => fetch(url))
.then((response) => response.json())
.then(processResponse)
.then(requestOutput)
.then(receiveOutput);
}
The function requestOutput also contains a fetch, but already the call of processResponse is delayed.
What can I change so that all steps are executed in the wanted sequence?
There's absolutely no reason to create new promises here. Passing a Promise instance to .then() is also incorrect as it requires one or two functions.
The .then() method always returns a new promise that resolves with the return value of the function provided
function actionOutput(chain) {
return chain.then(() => print('text'));
}
function actionLoad(chain) {
const url = '...';
return chain
.then(() => print('run action load\n')) // resolves with return value of
// `print()`, probably `undefined`
.then(() => fetch(url))
.then((response) => response.ok ? response.json() : Promise.reject(res))
.then(processResponse)
.then(requestOutput)
.then(receiveOutput);
}

Jest tests of Promises

I need to write a Jest test for a function using Promises, which is working perfectly in the browser.
My code is as follows: a JQuery AJAX call is done, which returns a promise; when this promise is resolved, I call another function, which is also using another Promises internally!
So the code of my Jest test is globally like this:
function post(url) {
return $.ajax(url).then((result) => {
resolve(result);
});
}
function showAlert(message) {
return new Promise((resolve, reject) => {
require('anotherPackage').then(() => { // require returns a Promise
anotherPackage.showAlert(message); // will result in DOM update
}).then(resolve, reject);
});
}
function handleResponse(result) {
const promises = [];
...
promises.push(showAlert(message));
...
return Promise.all(promises);
}
test("Promises test", () => {
let response = null,
url = 'http://example.com/result.json';
return post(url).then([result, status, request] => {
response = parseResponse(result);
}).then(() => {
return handleResponse(response).then(() => {
return $('.alert').length;
}).then(value => {
expect(value).toBe(1); // doesn't work, value is 0 !!!
});
});
});
The handleResponse function is using AJAX request response, and in this use case, the function is calling another function which is using a promise internally which, when resolved, is creating an alert inside the DOM.
Actually, the test given as example doesn't work, because the expect call is done before the inner handleResponse promise is "completely resolved"!
So my question is: how can I handle this use case with Jest?
Best regards,
Thierry

Why does the await call for my custom promise hang?

I am trying to test a function in my Express/Node backend using Mocha. I have created a stub of the actual parameter which is modified by the function: it has a send method which is called in getValue (the function I want to test) and a ready parameter which I initialize to a new promise when the stub is created and resolve when send is called on the stub.
I'm trying to await this promise, but it's just hanging (and then Mocha times out the test). The setTimeout below prints Promise { 'abc' }, which I think means the promise has resolved as I expect it to, but the await never completes.
This is the relevant code in the test file:
function ResStubTemplate() {
return {
_json: undefined,
_message: undefined,
json: function(x) {
this._json = x;
return this;
},
send: function(message) {
this._message = message;
this.ready = Promise.resolve("abc");
return this;
},
ready: new Promise(_ => {})
}
}
// This is the test
it("should get the value.", async function(done) {
let req = { query: { groupId: 12345 } };
res = ResStubTemplate();
controller.getValue(req, res);
setTimeout(() => console.log(res.ready), 1000); // prints Promise { 'abc' }
let x = await res.ready; // hangs??
console.log(x); // never prints
done();
}
This is the relevant code in the tested file:
exports.getValue = function(req, res) {
ValueModel.findOne({groupId: req.query.groupId})
.then(value => res.json({value: value}).send();
};
The error I get is:
Error: Timeout of 5000ms exceeded.
For async tests and hooks, ensure "done()" is called; if returning a Promise,
ensure it resolves. (/.../test/api/controller_test.js)
When the expression:
let x = await res.ready; // hangs??
… is evaluated, the value is the promise created by this code:
ready: new Promise(_ => {})
That promise never resolves, so it keeps waiting for it.
Later you do this:
this.ready = Promise.resolve("abc");
… which replaces that promise with a new (resolved) promise, but the new promise isn't the value that you are awaiting.

Is it possible to cancel a child promise when parent is cancelled?

I have this code (using Bluebird Promise):
const promise = loadSomething(id)
.then(something => {
loadParentOfSomething(something.parentId);
return something;
});
When I then do promise.cancel() the getSomething is cancelled, but the getSomethingParent is not.
Is there a way to, when getSomething promise is cancelled, I can also get the getSomethingParent promise to cancel?
Both load functions returns a cancellable async promise with an HTTP request, and the reason I want to cancel them is because they can sometimes take a while to load and when for example a user navigates away (SPA) the response is no longer needed.
I think what you are actually looking for
const promise1 = loadSomething(id);
const promise2 = promise1.then(something => { return loadParentOfSomething(something.parentId); });
// ^^^^^^
promise2.catch(e => void "ignore"); // prevent unhandled rejections
Then you can keep using promise1 to access the result, but also call promise2.cancel(). This cancellation will be possible even after promise1 settled.
Define a function as the second parameter of the then callback. Example:
const promise = getSomething(id)
.then(something => {
getSomethingParent(something.parentId);
return something;
}, error => {
console.error(error)
});
When you call promise.reject() then getSomethingParent will not be called.
Reference
If you prepare a dummy promise to reference loadSomethingOfParent you should be able to cancel it within loadSomething.
// Create a dummy promise to reference `loadParentOfSomething`
var dummyPromise = Promise.resolve();
// Pass `dummyPromise` to `loadSomething`
const promise = loadSomething(id, dummyPromise).then(something => {
dummyPromise = loadParentOfSomething(something.parentId);
return something;
});
loadSomething will need an onCancel handler which will execute when the promise is cancelled.
function loadSomething(id, promise) {
return new Promise(function(resolve, reject, onCancel) {
// Do your stuff
// The `.cancel()` handler
onCancel(function() {
promise.cancel();
});
});
}

How to Inspect fetch call and Return same Call

I need a way of taking a promise, calling .then on it to inspect the returned value, and then to return this promise exactly as it was to other parts of the system. The context is I'm trying to modify the fetch API from a GraseMonkey script so that I can modify the data returned. I inspect this by calling .json() on the response. However if I don't modify the data, I need to return an object exactly representing the original call to the fetch API so that the page's code sees no difference. But when I have tried returning the object, I get an error that the response has already been consumed and now I'm lost in a stack of Promises that I can't seem to get to work out (I'm not a JS native)
The code below is what I have already and it works, but it's not really acceptable as it duplicates every other request that's not being mangled.
function newFetch(){
if (needsToBeModified(arguments[0])) {
response = null;
return oldFetch.apply(this, arguments)
.then(r => {
response = r;
return r.json();
})
.then(
j => {
j = processJSON(j);
return new Promise((resolve, rej) => {
response.json = () => new Promise((resolve, reject) => resolve(j));
resolve(response);
});
},
(fail) => {
return oldFetch.apply(this, arguments)
//How can I avoid making this call here again?
}
);
} else {
return oldFetch.apply(this, arguments);
}
}
Please could someone tell me a way of peeking at the json, and if this throws an error, returning the original promise from fetch without needing to make the call again?
Thanks.
fetch() returns a promise that resolves to a response object. One of the methods on that response object is .clone() which sounds like it does just what you want. From the doc for .clone():
The clone() method of the Response interface creates a clone of a response object, identical in every way, but stored in a different variable.
clone() throws a TypeError if the response Body has already been used. In fact, the main reason clone() exists is to allow multiple uses of Body objects (when they are one-use only.)
I think you can use that like this:
function newFetch(){
let p = oldFetch.apply(this, arguments);
if (needsToBeModified(arguments[0])) {
let origResponse, cloneResponse;
return p.then(r => {
origResponse = r;
cloneResponse = r.clone();
return r.json();
}).then(j => {
j = processJSON(j);
// monkey patch .json() so it is a function that resolves to our modified JSON
origResponse.json = () => Promise.resolve(j);
return origResponse;
}, fail => {
// return clone of original response
if (cloneResponse) {
return cloneResponse;
} else {
// promise was rejected earlier, don't have a clone
// need to just propagate that rejection
throw fail;
}
});
} else {
return p;
}
}
Like #jfriend suggested, you will need to clone the response so that your caller can consume it again after you inspected the JSON:
function newFetch(urlOrRequest) {
var promise = oldFetch.apply(this, arguments);
if (needsToBeModified(urlOrRequest)) {
return promise.then(response =>
response.clone().json().then(obj => {
const result = Promise.resolve(processJSON(obj));
response.json = () => result;
return response;
}, jsonErr => {
return response;
})
);
} else {
return promise;
}
}
Or better yet, instead of returning the old, unaltered response with a patched json method, just create a new one:
function newFetch(urlOrRequest) {
var promise = oldFetch.apply(this, arguments);
if (needsToBeModified(urlOrRequest)) {
return promise.then(response =>
response.clone().json().then(obj => {
const result = JSON.stringify(processJSON(obj));
return new Response(result, response);
}, jsonErr => response)
);
} else {
return promise;
}
}
Alternatively, the easiest way to go would be to defer your processJSON call into the overwritten json method:
…
if (needsToBeModified(urlOrRequest)) {
return promise.then(response =>
response.json = function() {
return Response.prototype.json.call(this).then(processJSON);
};
return response;
});
}
…
I think I understand that question to say that sometimes you need to alter the result of an async operation depending it's arguments
There's no need to perform the operation twice, and I think the OP code can be dramatically simplified as follows:
function newFetch() {
return oldFetch.apply(this, arguments).then(r => {
return (needsToBeModified(arguments[0]))? processJSON(r.json()) : r;
});
}

Categories

Resources