Webworker return result after callback - javascript

I have an Angular service where I'm using $q service in combination with webworkers. In my original function before using webworkers, my completeClass function would return an object.
I replaced the code to post a message to my new web worker script.
The callback of the webworker is in my initWorkers function where I add the eventlistener.
My goal is that the completeClass function returns the result of the webworker. How can I make this happen?
this.classWorker = new Worker('app/shared/autocomplete/autocomplete-class-worker.js');
this.completeClass = function(text){
var self = this;
var defer = $q.defer();
classWorker.postMessage([text, this.oldText, this.oldProposals, this.aliases, this.entityClasses])
};
this.initWorkers = function(){
var self = this;
worker.addEventListener('message', function(e) {
defer.resolve(e.data);
self.oldProposals = e.data[1];
self.oldText = text;
return e.data[0];
}, false);
};

If you are going to call the worker when the previous call is still running, then you need something to either queue or keep track of in-progress requests. My suspicion is that unless you need control of the queue of requests, it's simpler for UI thread to fire off the requests to the worker, so the browser essentially queues the requests for you.
But you would still need to keep track of the requests sent somehow, so when you get a message back from the worker, you know which one it is responding do. You can do this by, in the main thread
Generating a unique ID for each request. An ever-increasing integer can be enough for a lot of cases.
Creating a deferred object and storing it, associated with the ID
Firing off the request to the worker, passing the ID
Passing a the promise of the deferred object back to the caller
The worker then
Receives the message, with the ID
Does its' work
Posts the result back, along with the ID
The main thread then
Receives the message, with the ID and result of the work.
Retrieves the deferred object by the ID, and resolves it with the results of the work.
To do this, you can use some code like below. I've slightly simplified your case by passing just text to the worker, getting completeText back. You can add more information going either way in a real case.
app.service('AutoComplete', function($q) {
var id = 0;
var worker = new Worker('app/shared/autocomplete/autocomplete-class-worker.js');
var inProgress = {};
this.completeClass = function(text) {
var deferred = $q.defer();
id++;
var request = {
id: id,
text: text
};
inProgress[id] = deferred;
worker.postMessage(request);
return deferred.promise;
};
worker.onmessage = function(e) {
var response = e.data;
var id = response.id;
var type = response.type; // resolve, reject, or notify
var completeText = response.completeText;
inProgress[id][type](completeText);
if (type === 'resolve' || type === 'reject') {
delete inProgress[id];
}
};
});
Then in the worker you can have code like:
self.onmessage = function(e) {
var request = e.data;
var text = request.text;
var id = request.id;
// Do the work here
var completeText = ...
var response = {
id: id,
type: 'resolve', // Can reject the promise in the main thread if desired
completeText: completeText
};
self.postMessage(response);
};
My goal is that the completeClass function returns the result of the webworker. How can I make this happen?
To clarify, it can't directly return the result, because the result is calculated asynchronously, and all function calls must return synchronously. But it can return a promise that resolves to the result later, just like $http does for example. To use the above, you can do something like
app.controller('MyController', function($scope, AutoComplete) {
$scope.complete = function(text) {
AutoComplete.completeClass(text).then(function(result) {
// Do something with result
});
});
});
Note: technically, passing an ID along with each request isn't necessary if the worker does all its' work synchronously on one request, and so responds to each call in the order received. In the above example, the main thread can always assume the calls to the worker make a first-in-first-out queue. However, passing an ID gives the flexibility of the worker not finishing the work in the order received. Say in a later version it needs to do something asynchronous, like call another worker, or make an ajax request, this method will allow that.

Related

How do I store the results of multiple asynchronous $.get requests in an array?

I have a list of items being stored in an array. For each item in the array, I need to make an API request and add some of the data that comes back into another array so that I can perform operations on it later.
I think the issue is that my get request is asynchronous, and as a result the data is not necessarily loaded when I am trying to add it to the array, but I thought that's what .then was supposed to cover.
var cardRequestList = ["Scapeshift","Ghostform", "Daybreak Chaplain"]
var url = "https://api.magicthegathering.io/v1/cards?name=%22";
var cardInfo =[];
for (var cardName in cardRequestList){
var results = getCard(cardRequestList[cardName]);
cardInfo.push(results);
}
console.log(cardInfo); // results should come back here
function getCard(cardName){
var cardUrl = url.concat(cardName,"%22");
$.get(cardUrl).then(
function(data) {
var temp = [data.cards[0].name,data.cards[0].printings]
return temp;
}, function() {
alert( "$.get failed!" );
}
);
}
then() only works for the specific request it's invoked on.
To solve the issue you have you could make your requests in a loop, adding the jqXHR object returned from those calls to an array which you can then apply to $.when() to execute some other logic once all the requests have completed.
Within the requests themselves you need to add the returned data to an array as you cannot return anything from an async call.
With all that said your code would look something like this:
var cardRequestList = ["Scapeshift", "Ghostform", "Daybreak Chaplain"]
var url = "https://api.magicthegathering.io/v1/cards?name=%22";
var cardInfo = [];
var requests = cardRequestList.map(function(cardName) {
return getCard(cardName);
});
function getCard(cardName) {
var cardUrl = url.concat(cardName, "%22");
return $.get(cardUrl).then(function(data) {
cardInfo.push([data.cards[0].name, data.cards[0].printings]);
}, function() {
alert("$.get failed!");
});
}
$.when.apply($, requests).done(function() {
// all requests finished and cardInfo has been populated, place logic here...
console.log(cardInfo);
});

I would like to execute regular function in async way

Consider this sample (say this is module)
function Calculator(value){
return {
add: function(value2){
return: {
value: function(){
return value + value2;
}
}
}
}
}
This is a class, and requires an argument when initialization, sample usage:
var Calculator = require('calculator_app_module');
var myCalc = new Calculator(1); // initialized with 1
myCalc.add(2).value(); // === 3;
Which is obviously expected, what i want is to execute add function in async way, just like that
var Calculator = require('calculator_app_module');
var myCalc = new Calculator(1); // initialized with 1
myCalc.add(2).value() ==== 3 // this executes in 2secs (async)
// and then returns result
I would like to patch Calculator.add method so that it can work with async
function patch(module){ //module is Calculator class
var oldAdd = Calculator.add;
Calculator.add = function(){
// some magic
// trigger event or whatever
oldAdd.apply(Calculator, arguments);
}
}
INDEX.JS
var Calculator = require('calculator_app_module');
var calc = new Calculator(1);
calc.add(2).value() === 3; // equalize within 2 seconds
// after async call is done
calc.add(2).value().equal(3); // also legit
The problem is that calc.add(n) returns new function value which is undefined in async call, is there a way to get the calling fn of add and call it back when result comes
update
Prior to #Zohaib Ijaz answer, you cannot modify content/logic of package, only extend/patch, Package must return same API but in promise way, no code breaking
calc.add(2).value() === 3; // sync code
calc.add(2).value() === 3; // async code
calc.add(2).value().equal(3); // async code
How to achieve
update
According to #Zohaib Ijaz comment, this also legit
myCalc.add(2).value().equal(3); //async
Point is in converting sync to async without breaking package, but extending the outcome
If you request a result by calling a chain of methods, like this:
a = myCalc.add(2).value();
or this:
myCalc.add(2).value().equal(3);
then there is no possibility to retrieve and use results that become available only asynchronously (i.e. later, after the statement has been evaluated). Note that asynchronous involves some event being put in the event queue. The currently executing code must finish first (i.e. until the call stack is empty), before that event can get processed.
The above syntax is useful for immediate evaluation only. In order to process asynchronous results you need to provide a call-back function somewhere for being informed about those results.
So with an asynchronous dependency in the add method, your code could provide a callback to the add method, which it would call when it has received the asynchronous result:
myAsyncCalc.add(2, function (added) {
a = added.value();
});
Or, when using promises (which is really nice to work with), the add method would return an object to which you can assign the same call-back:
myAsyncCalc.add(2).then(function (added) {
a = added.value();
});
Note that the callback function is not part of the currently executing code. It just is a function reference, that can be used at a later, asynchronous event for calling you back. But that will be part of a separate execution sequence, that only starts when the internal event queue has been processed and an event has been processed that triggered that execution sequence.
If this is not an acceptable solution, and you really need the former syntax to somehow take an asynchronous produced result into account, then you are without hope: it is not possible, because that really represents synchronous code execution.
Wrapping your Object
You write that you cannot modify the content of the package, but can only extend it.
One way to do that is to make use of proxies.
The idea is that you trap a reference to the add method, and return
your own adapted version of the method, which can optionally still call the original method.
See the above referenced MDN article for examples.
Calling HTTP request Synchronously
If you really want to write code like this:
a = myCalc.add(2).value();
even when the implementation of add performs an HTTP request, then you could have a look at making the HTTP request synchronously. But it should be noted that this is considered bad practice.
Code Example
Here is code that performs the addition in three ways:
unmodified (synchronous)
with an asynchronous HTPP call
with a synchronous HTTP call
For the two modified versions, a proxy pattern is used. For the asynchronous example, a call back is used using the Promise pattern.
Code:
// code in module is not modified
function Calculator(value){
return {
add: function(value2){
return {
value: function(){
return value + value2;
}
}
}
}
}
// standard object creation
var myCalc = new Calculator(1); // initialized with 1
// Create a proxy for the above object, which will expose
// an asynchronous version of the "add" method. Note that the
// "myCalc" object is not modified.
var myCalcHttpAsync = new Proxy(myCalc, {
get: function(myCalc, name) {
if (name !== 'add') return myCalc[name]; // pass-through
return function(value2) {
return new Promise(function(resolve, reject) {
// Define some url
var url = 'http://api.stackexchange.com/2.2';
// Perform HTTP request
var request = new XMLHttpRequest();
// Define call back for when response becomes available
request.onload = function() {
if (request.readyState !== 4) return;
// When async task notifies it has finished:
// call the original "add" method and notify those
// waiting for the promise to get resolved
resolve(myCalc.add(value2));
};
// `true` as third argument makes the request asynchronous
request.open('GET', url, true);
request.send(null);
});
};
}
});
// Create another, alternative proxy for demonstrating
// synchronous HTTP call:
var myCalcHttpSync = new Proxy(myCalc, {
get: function(myCalc, name) {
if (name !== 'add') return myCalc[name]; // pass-through
return function(value2) {
// Define some url
var url = 'http://api.stackexchange.com/2.2';
// Perform HTTP request
var request = new XMLHttpRequest();
// `false` as third argument makes the request synchronous
request.open('GET', url, false);
// code execution "hangs" here until response arrives
request.send(null);
// process response...
var data = request.responseText;
// .. and return the value
return myCalc.add(value2);
};
}
});
// I/O
var std = document.getElementById('std');
var async = document.getElementById('async');
var sync = document.getElementById('sync');
// 1. Standard
std.textContent = myCalc.add(2).value();
// 2. Asynchronous HTTP
myCalcHttpAsync.add(2).then(function (added) {
// This needs to happen in a callback, otherwise it would be synchronous.
async.textContent = added.value();
});
// 3. Synchronous HTTP
sync.textContent = myCalcHttpSync.add(2).value();
Unmodified result: <span id="std">waiting...</span><br>
Result after asynchronous HTTP call: <span id="async">waiting...</span><br>
Result after synchronous HTTP call: <span id="sync">waiting...</span><br>
Here is my solution using promise.
Here is a link to jsbin where you can execute the code.
http://jsbin.com/qadobor/edit?html,js,console,output
function Calculator(value) {
return {
add: function(value2) {
return new Promise(function(resolve, reject) {
setTimeout(
function() {
resolve(value + value2);
}, 2000);
});
}
};
}
var myCalc = new Calculator(1); // initialized with 1
myCalc.add(2).then(function(ans){
// this callback will be called after 2 seconds after promise resolve.
console.log(ans);
});

Async Code to execute sequentially

i am facing an issue. i have angularjs function which in turn call to another angularjs function which has post request.This post request always fire at last when first function ends..it is not fired sequntially.
servicePOST.send(appConstants.BASE_MS_URL + 'Dcrs/activityDay.php',{
"date":d
}).then(function(result) {
console.log(result);
});
Please someone explain me this behaviour..any workaround for this...i want to execute all http request sequentially.how do i implemet it to this code? Thanks in advance!
This can help you
var TestService = function( $scope, servicePOST, AnotherService2, AnotherService3 )
{
// Level 1
servicePOST
.send(appConstants.BASE_MS_URL + 'Dcrs/activityDay.php',{
"date":d })// Request #1
.then( function( result ) // Response Handler #1
{
$scope.dcrlocked = result.dcrlocked;
// Level 2
AnotherService2 //AnotherService Call
.send({}) // Request #2
.then( function( result ) // Response Handler #2
{
$scope.leaves = result.leaves;
// Level 3
AnotherService3
.send({}) // Request #3
.then( function( result ) // Response Handler #3
{
//$scope.holidays = result.holidays;
});
});
});
};
As I understand from your question, you have a page where one section depends on other section and so on. and all these sections are being served/rendered by different-2 http request.
In this case, you can make the second http request from the success/resolved callback of first http request and so on. e.g.
servicePOST.send(appConstants.BASE_MS_URL + 'Dcrs/activityDay.php',{
"date":d
}).then(function(result) {
$scope.dcrlocked = result.dcrlocked;
$scope.leaves = result.leaves;
//$scope.holidays = result.holidays;
//make another http request as below.
servicePOST2.send(url,{data or data from last request}).then(function(){
// make another http request. and so on.
})
});
As all the http requests are being made from the success callback of last http request, it will guarantee sequential http requests.
EDIT
you can make use of $promise in your second function, where you are calling post request. e.g.
var deferred = $q.defer();
servicePOST.send(appConstants.BASE_MS_URL + 'Dcrs/activityDay.php',{
"date":d
}).then(function(result) {
$scope.dcrlocked = result.dcrlocked;
$scope.leaves = result.leaves;
//$scope.holidays = result.holidays;
deferred.resolve(result);
});
return deferred; // return deferred from your function.
Don't forget to inject $q in you controller and then passing it to second function. This will make post function to return synchronously. Let me know if this what you are looking for.
In short you can't. Javascript is non-blocking by design, you need to take a look at promises or implement nested callbacks.

New $.Deferred object with the old callbacks

Please forgive me if this is a stupid question. I have been trying for hours and my brain have just stopped working.
I have such system that consists of three AJAX calls. Server response of first call usually is a 200 Success; but second and third queries are fragile because they are image uploading, and on the server side, I have so much validation rules that client's images mostly fail.
window.AjaxCall = function () {
// to pass to $.ajax call later
this.args = arguments;
// xhr status
this.status = null;
// xhr results (jqXHR object and response)
this.xhrResponse = {};
this.dfr = new $.Deferred();
// to provide an easier interface
this.done = this.dfr.done;
this.fail = this.dfr.fail;
this.then = this.dfr.then;
};
AjaxCall.prototype.resetDfr = function () {
this.dfr = new $.Deferred();
};
AjaxCall.prototype.resolve = function () {
this.dfr.resolve(
this.xhrResponse.result,
this.xhrResponse.jqXHR
);
this.resetDfr();
};
AjaxCall.prototype.reject = function () {
this.dfr.reject(
this.xhrResponse.jqXHR
);
this.resetDfr();
};
AjaxCall.prototype.query = function () {
var _this = this;
// if query hasn't run yet, or didn't return success, run it again
if (_this.status != 'OK') {
$.ajax.apply(_this, _this.args)
.done(function (result, textStatus, jqXHR) {
_this.xhrResponse.result = result;
_this.xhrResponse.jqXHR = jqXHR;
_this.resolve();
})
.fail(function (jqXHR) {
_this.xhrResponse.jqXHR = jqXHR;
_this.reject();
})
.always(function (a, b, c) {
var statusCode = (typeof c !== 'string'
? c
: a).status;
if (statusCode == 200) {
_this.status = 'OK';
}
});
}
// if query has been run successfully before, just skip to next
else {
_this.resolve();
}
return _this.dfr.promise();
};
AjaxCall class is as provided above, and I make the three consecutive calls like this:
var First = new AjaxCall('/'),
Second = new AjaxCall('/asd'),
Third = new AjaxCall('/qqq');
First.then(function () {
console.log('#1 done');
}, function() {
console.error('#1 fail');
});
Second.then(function () {
console.log('#2 done');
}, function() {
console.error('#2 fail');
});
Third.then(function () {
console.log('#3 done');
}, function() {
console.error('#3 fail');
});
var toRun = function () {
First.query()
.then(function () {
return Second.query();
})
.then(function () {
return Third.query()
});
};
$('button').click(function () {
toRun();
});
Those code are in a testing environment. And by testing environment, I mean a simple HTML page and basic server support for debugging.
Home page (/) always returns 200 Success.
/asd returns 404 Not Found for the first 3 times and 200 Success once as a pattern (i.e. three 404s -> one 200 -> three 404s -> one 200 -> three 404s -> ... ).
/qqq returns 404 Not Found all the time.
When I click the only button on the page, first query returns success and second fails as expected. When I click the button second time, first query skips because it was successful last time and second fails again, also as expected.
The problem here is:
before I used the resetDfr method because the dfr is alreay resolved or rejected, it doesn't react to resolve and reject methods anymore.
When I call the resetDfr method in the way I show in the example, dfr is able to get resolved or rejected again, but the callbacks of the old dfr are not binded with the new dfr object and I couldn't find a way to clone the old callbacks into the new dfr.
What would be your suggestion to accomplish what I'm trying to do here?
Promises represent a single value bound by time. You can't conceptually "reuse" a deferred or reset it - once it transitions it sticks. There are constructs that generalize promises to multiple values (like observables) but those are more complicated in this case - it's probably better to just use one deferred per request.
jQuery's AJAX already provides a promise interface. Your code is mostly redundant - you can and should consider using the existent tooling.
Let's look at $.get:
It already returns a promise so you don't need to create your own deferred.
It already uses the browser cache, unless your server prohibits HTTP caching or the browser refuses it only one request will be made to the server after a correct response arrived (assuming you did not explicitly pass {cache: false} to its parameters.
If making post requests you can use $.post or more generally $.ajax for arbitrary options.
This is how your code would roughly look like:
$("button").click(function(){
var first = $.get("/");
var second = first.then(function(){
return $.get("/asd");
});
var third = second.then(function(){
return $.get("/qqq");
});
});
The reason I put them in variables is so that you will be able to unwrap the result yourself later by doing first.then etc. It's quite possible to do this in a single chain too (but you lose access to previous values if you don't explicitly save them.
For the record - it wasn't a stupid question at all :)

How to use data returned by $http request for another request in same controller using angularjs?

I am making a function that makes a http GET request, and then uses part of that response to make another http GET request. However, the data being returned by the first http GET request is wrapped in a lot of unnecessary data (maybe its a promise object), but I just need the json component of it. How do I access the json data of my response in the controller? Here is my code.
$scope.doSearch = function() {
var upcResult = Restangular.one('upc',$scope.searchTerm).get()
//the upc returns an item, so i am trying to access that part of the json by using
upcResult.item, how if I console.log that it is undefined
$scope.doAnotherSearch = function() {
var itemResult = Restangular.one('item',upcResult.item).get();
}
You can use promise chain.
var upcResult = Restangular.one('upc',$scope.searchTerm).get();
upcResult.then(function (result) {
// call other http get
return Restangular.one('item',result.item).get();
}).then(function (result) {
//....
});
I don't know if in your case Restangular.one(/*...*/).get(); returns promise but you can wrap it with $q like:
var upcResult = Restangular.one('upc',$scope.searchTerm).get();
var deferred = $q.defer();
deferred.resolve(upcResult).then(function(){/*...*/});

Categories