I read the Promise/A+ specification and it says under 2.2.4:
onFulfilled or onRejected must not be called until the execution context stack contains only platform code
But in Firefox (I tested 38.2.1 ESR and 40.0.3) the following script executes the onFulfilled method synchronously:
var p = Promise.resolve("Second");
p.then(alert);
alert("First");
(It does not seem to run using alerts here, it can also be tried here: http://jsbin.com/yovemaweye/1/edit?js,output)
It works as expected in other browsers or when using the ES6Promise-Polyfill.
Did I miss something here? I always though that one of the points of the then-method is to ensure asynchronous execution.
Edit:
It works when using console.log, see answer by Benjamin Gruenbaum:
function output(sMessage) {
console.log(sMessage);
}
var p = Promise.resolve("Second");
p.then(output);
output("First");
As he points out in the comments, this also happens when using synchronous requests, which is exactly why it happens in your test scenario.
I created a minimal example of what happens in our Tests:
function request(bAsync) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.addEventListener("readystatechange", function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
resolve(xhr.responseText);
}
});
xhr.open("GET", "https://sapui5.hana.ondemand.com/sdk/resources/sap-ui-core.js", !!bAsync);
xhr.send();
});
}
function output(sMessage, bError) {
var oMessage = document.createElement("div");
if (bError) {
oMessage.style.color = "red";
}
oMessage.appendChild(document.createTextNode(sMessage));
document.body.appendChild(oMessage);
}
var sSyncData = null;
var sAsyncData = null;
request(true).then(function(sData) {
sAsyncData = sData;
output("Async data received");
});
request(false).then(function(sData) {
sSyncData = sData;
output("Sync data received");
});
// Tests
if (sSyncData === null) {
output("Sync data as expected");
} else {
output("Unexpected sync data", true);
}
if (sAsyncData === null) {
output("Async data as expected");
} else {
output("Unexpected async data", true);
}
In Firefox this leads to:
This is because you're using alert
When you use alert here it blocks and all bets are off - the page has frozen, execution halted and things are at "platform level".
It might be considered a bug, and it's certainly not what I would expect - but at the core this is about the incompatibility between alert and JavaScript task/microtask semantics.
If you change that alert to a console.log or appending to document.innerHTML you'd get the result you expect.
var alert = function(arg) { // no longer a magical and blocking operation
document.body.innerHTML += "<br/>" + arg;
}
// this code outputs "First, Second, Third" in all the browsers.
setTimeout(alert.bind(null, "Third"), 0);
var p = Promise.resolve("Second");
p.then(alert);
alert("First");
From what I can tell, this is actually permitted optional behavior:
Optionally, pause while waiting for the user to acknowledge the message.
(Emphasis mine)
Basically, what firefox does is this:
Execute until it encounters the first alert.
Run any microtasks to completion before pausing (tasks are paused, and not microtasks).
The then is run as a microtask, so "Second" gets queued and precedes the alert.
The Second alert gets run.
The First alert gets run.
Confusing, but allowed from what I can tell.
Related
My issue seems to be the server is listening on a websocket for certain events and then when a certain event is heard it is supposed to fire its own event through a different websocket, but that event is rarely fired. With the conditional statement inside function availabilityCheck removed, the event is always fired instead of rarely. There are 2 sockets websockets the server is connected on for clarity.
event heard from websocket(1) usually 2-4 times within milliseconds-> backend does logic (but only once event though the event was fired 2-4 times) -> event supposed to fire to websocket(2)
let obj = {available: 0}
if (event.Event == 'AgentConnect') {
const agentExtension = '1001'
availabilityCheck(event, agentExtension)
.then(function () {
socket.emit('AgentConnect', agentExtension); //works rarely, but always works when the if statement inside availabilityCheck() is removed
}).catch(function(err){
console.log(err);
})
}// end if
function availabilityCheck(evt, agentExt) {
return promise = new Promise(function (resolve, reject) {
if (obj.available == 0) {// When this conditional is removed the socket event always fires
obj.available =1;
resolve();
} else {
reject('agent unavailable');
}
})
}
Could you try the following:
var obj = (x =>{
var available = 0;
return {
set available(val) {
debugger;
available=val;
},
get available() {
return available;
}
}
})();
Now it should pause when you change available, if you have the dev tools open (press F12) you can check the stack trace and see what other code is changing available while the Promise is resolving.
The code you provided does not show any problem, available should still be 0 but I'm sure there is more code.
If you want something to happen in serie then you could run the promise in serie instead of parallel:
let agentConnectPromise = Promise.resolve()
if (event.Event == 'AgentConnect') {
agentConnectPromise =
agentConnectPromise
.then(
x=> availabilityCheck(event, agentExtension)
)
.then(...
Moving the assignment of obj.available to the then() fixed my issue.
if (event.Event == 'AgentConnect') {
const agentExtension = '1001'
availabilityCheck(event, agentExtension)
.then(function () {
obj.available = 1; //moved from availability to check to here.
socket.emit('AgentConnect', agentExtension); //works rarely, but always works when the if statement inside availabilityCheck() is removed
}).catch(function(err){
console.log(err);
})
}// end if
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);
});
I have a page that chains two API calls, loads the data into first_data and second_data before executing a createPage function (which is several kb of data manipulation and d3.js):
template.html
<script src="createPage.js"></script>
<script>
var first_data, second_data = [], [];
function getFirstData(){
return new Promise(function(resolve) {
var xhr = new XMLHttpRequest();
var url = "/API/my-request?format=json"
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
first_data = JSON.parse(xhr.responseText);
resolve('1');
}
}
xhr.open("GET", url, true);
xhr.send();
});
} //similar function for getSecondData()
getFirstData()
.then(getSecondData)
.then(createPage(first_data, second_data));
</script>
The trouble is that some of the code that manipulates the data in createPage is showing errors, for example "can't convert undefined to object". In that particular error's case, it's because I try to do Object.keys(data[0]) on some data that should be loaded from the API requests. Some observations:
If I inspect the data in the browser dev console, it's all there.
If I just paste the code from the file in the console, the page draws fine.
If I hard-code the initializing arrays etc for the data manipulation part of the code (to get rid of the can't convert undefined, then the page draws but all the graphics indicate that they were populated with no data.
The page loads fine if I put the the JSON data in a .js file and load it as a script just before the createPage.js file at the end of the body.
I inserted a console.log("starting") statement at the start and end of createPage(). Looking at the network and js console output when I load, the starting output occurs before the two API GET requests are displayed in the network activity. Is this representative of what's really happening (i.e. can you mix javascript console and network console timing?)
So, clearly I don't have access to the data at the point when I need it.
Why? Are my Promises incorrect?
How can I fix this?
Promise.prototype.then() expects 2 arguments(onFulfilled & onRejected) as function-expression(OR handler or callback) as it is a function(handler) which will be invoked when Promise is fulfilled
In your case, createPage(first_data, second_data) will invoke the function createPage when statement is interpreted by interpreter.
Use anonymous function as an argument and invoke your function inside it.
getFirstData()
.then(getSecondData)
.then(function() {
createPage(first_data, second_data);
});
Edit: If you are not passing any arguments specifically to the callback, you can use .then(FUNCTION_NAME)
In functional programming and using promises, you should probably refactor getFirstData (and getSecondData) to the following form:
function getFirstData(){
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
var url = "/API/my-request?format=json"
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
// Resolve the result, don't assign it elsewhere
resolve(JSON.parse(xhr.responseText));
} else {
// Add rejection state, don't keep the promise waiting
reject("XHR Error, status = ", xhr.status);
}
}
xhr.open("GET", url, true);
xhr.send();
});
}
And then resolve the promises like this (assume first & second data is not dependant on each other)
Promise.all([getFirstData(), getSecondData()]).then(function(allData){
var first_data = allData[0];
var second_data= allData[1];
return createPage(first_data, second_data);
}).catch(function(error){
console.log("Error caught: ", error);
});
To make things even cleaner, you can change createPages's from:
function createPage(first_data, second_data){
// Function body
}
to
function createPage(data){
var first_data = data[0];
var second_data= data[1];
// Function body
}
and the Promise part:
Promise.all([getFirstData(), getSecondData()]).then(createPage);
Notice how short it became?
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 :)
I have JavaScript code like this:
var buffer=new Array();
function fetchData(min,max){
var ajaxReq = new XMLHttpRequest();
ajaxReq.onreadystatechange = function(){
if (ajaxReq.readyState === 4) {
if (ajaxReq.status === 200) {
buffer= ajaxReq.responseText;
console.log(buffer)//this logs an array to console
} else {
console.log("Error", ajaxReq.statusText);
}
}
};
ajaxReq.open('GET', "server/controller.php?min="+min+"&max="+max, true);
ajaxReq.send();
}
fetchData(1,100);
console.log(buffer);//this log an empty array
two logs with different result, what am I doing wrong? thanks for pointers.
Ajax is asynchronous. That means that console.log(buffer) at the end is executed before the response from the Ajax request.
You should change your method to this:
function fetchData(min,max,callback){
var ajaxReq = new XMLHttpRequest();
ajaxReq.onreadystatechange = function(){
if (ajaxReq.readyState === 4) {
if (ajaxReq.status === 200) {
buffer= ajaxReq.responseText;
callback();
//console.log(buffer)//this logs an array to console
} else {
console.log("Error", ajaxReq.statusText);
}
}
};
ajaxReq.open('GET', "server/controller.php?min="+min+"&max="+max, true);
ajaxReq.send();
}
fetchData(1,100,function(){
console.log("My Ajax request has successfully returned.");
console.log(buffer);
});
You are trying to log() the buffer before the AJAX request in executed. To solve this, your fetchData function needs to handle a callback function.
var buffer=new Array();
function fetchData(min,max, callback){
var ajaxReq = new XMLHttpRequest();
ajaxReq.onreadystatechange = function(){
if (ajaxReq.readyState === 4) {
if (ajaxReq.status === 200) {
buffer= ajaxReq.responseText;
console.log(buffer)//this logs an array to console
if(typeof callback == 'function'){
callback.call(this);
}
} else {
console.log("Error", ajaxReq.statusText);
}
}
};
ajaxReq.open('GET', "server/controller.php?min="+min+"&max="+max, true);
ajaxReq.send();
}
fetchData(1,100, function(){
console.log(buffer);
});
This is the most basic implementation, and will work only if the AJAX response is successful.
This is asynchronous. So your flow goes like this:
call fetchData()
ajax request is sent, registering an onreadystatechange callback
fetchData() completes and returns
buffer is logged out, which doesn't yet contain anything.
Sometime later, the ajax request completes and triggers the callback
The callback puts things in the array.
buffer get's logged out from the callback, and you see it now has items in it.
So you are only starting the asynchronous request once you hit that first console.log. But it actually finishes long afterward.
A couple of issues here. When the ajax call completes the 2nd console.log has already executed before the variable was set.
Also,You're not using the buffer varaible as an Array.
Seems right to me. buffer is empty to start and it doesn't get set until AFTER the asynchronous call is made, so even though you're fetchingData before the second console.log, you're not receiving it until after it shows an empty buffer.
MDN: https://developer.mozilla.org/en/XMLHttpRequest
void open(in AUTF8String method, in AUTF8String url, in boolean async, in AString user, in AString password);
The third argument is used to tell the browser whether the request should be made asynchronous or not. You set it to true, thus it will be async.
Async basically means that the request is sent and meanwhile other code is executed. So, it starts the request, and while waiting for a response, it logs the buffer: before the request has finished. If you want to log the contents, do it in the onreadystatechange event or set the third argument (async) to false.