I'm replacing $http with Fetch API and got replaced $q with Promise API. Because of that, Angular didn't run digest cycles anymore, thus UI didn't render. To solve this problem I tried Zone.js and that seems to solve our problems partially. Unfortunately its API completely changed in 0.6 so we're using legacy 0.5.15.
Now to the actual problem.
When refreshing the page Angular configs and bootstraps the application like expected. In this phase I'm initializing the Zone and decorating the $rootScope.apply with the Zone and $rootScope.$digest(). Now when I transition between states/routes (with ui-router) everything works as expected, but when full refreshing there's a race condition and the zone/digest doesn't run correctly. I'm not sure how to fix it.
I have the following code in a angular.run() block:
console.log('Zone setup begin');
const scopePrototype = $rootScope.constructor.prototype;
const originalApply = scopePrototype.$apply;
const zoneOptions = {
afterTask: function afterTask() {
try {
$rootScope.$digest();
} catch (e) {
$exceptionHandler(e);
throw e;
}
}
};
scopePrototype.$apply = function $applyFn() : void {
const scope = this;
const applyArgs = arguments;
window.zone.fork(zoneOptions).run(() => {
originalApply.apply(scope, applyArgs);
console.log('Zone + $digest run!');
});
};
console.log('Zone setup end');
Above you can see that I log to the console when the Zone initialization begins, when it ends and when it's run (+ Angular digest cycle). In my controller where I fetch the data via Fetch API I've added a console.log('Data fetched!'); so I know when the data has been fetched.
Now the output in console:
State transition with ui-router (works perfectly)
Notice that the digest is run in the end.
Zone setup begin
Zone setup end
Zone + $digest run!
Zone + $digest run!
Zone + $digest run!
Zone + $digest run!
Data fetched!
Zone + $digest run!
Full refresh on state/route (doesn't run in the end)
Zone setup begin
Zone setup end
Zone + $digest run!
Zone + $digest run!
Zone + $digest run!
Zone + $digest run!
Data fetched!
As you can see the Zone/digest doesn't run after the data is fetched, which is why the data and UI isn't rendered on the page.
Convert the ES6 promises created by the fetch API to AngularJS $q promises with $q.when.
Use $q.when to convert ES6 promises to AngularJS promises1
AngularJS modifies the normal JavaScript flow by providing its own event processing loop. This splits the JavaScript into classical and AngularJS execution context. Only operations which are applied in the AngularJS execution context will benefit from AngularJS data-binding, exception handling, property watching, etc...2 Since the promise comes from outside the AngularJS framework, the framework is unaware of changes to the model and does not update the DOM.
Use $q.when to convert the external promise to an Angular framework promise:
var myRequest = new Request('flowers.jpg');
$q.when(fetch(myRequest)).then(function(response) {
//code here
})
Use $q Service promises that are properly integrated with the AngularJS framework and its digest cycle.
$q.when
Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. This is useful when you are dealing with an object that might or might not be a promise, or if the promise comes from a source that can't be trusted.
-- AngularJS $q Service API Reference - $q.when
Rationale
Wrapping $q.when will work but in my team's experience it will be very finicky and prone to error. As one example, returning $q.when from inside the body of a Promise.then function will still chain as a regular Promise and you won't get a $digest on callbacks.
It also requires all authors to understand the difference between two very similar looking constructs (Promise/$q) and care about their concrete types for every level of an asynchronous call. If you are using modern conveniences like async/await (which abstracts the Promise types further), you're gonna be in even more trouble. Suddenly none of your code can be framework agnostic.
Our team decided it was worth committing a big monkey patch to ensure all the promises (and the async/await keywords) "just worked" without needing additional thinking.
Ugly? Yes. But we felt it was an okay tradeoff.
Patch Promise callbacks to always apply $rootScope
First we install the patch against Promise in a angular.run block:
angular.module(...).run(normalizePromiseSideEffects);
normalizePromiseSideEffects.$inject = ['$rootScope'];
function normalizePromiseSideEffects($rootScope) {
attachScopeApplicationToPromiseMethod('then');
attachScopeApplicationToPromiseMethod('catch');
attachScopeApplicationToPromiseMethod('finally');
function attachScopeApplicationToPromiseMethod(methodName) {
const NativePromiseAPI = window.Promise;
const nativeImplementation = NativePromiseAPI.prototype[methodName];
NativePromiseAPI.prototype[methodName] = function(...promiseArgs) {
const newPromiseArgs = promiseArgs.map(wrapFunctionInScopeApplication);
return nativeImplementation.bind(this)(...newPromiseArgs);
};
}
function wrapFunctionInScopeApplication(fn) {
if (!isFunction(fn) || fn.isScopeApplicationWrapped) {
return fn;
}
const wrappedFn = (...args) => {
const result = fn(...args);
// this API is used since it's $q was using in AngularJS src
$rootScope.$evalAsync();
return result;
};
wrappedFn.isScopeApplicationWrapped = true;
return wrappedFn;
}
}
Async/Await
If you want to support the use of async/await, you'll also need to configure Babel to always implement the syntax as Promises. We used babel-plugin-transform-async-to-promises.
Related
I'm writing an AngularJS plugin for Umbraco and have created a simple view, controller and service. But for some reason my promise is taking a while to resolve.
I have used the inbuilt $q service to create and return my promise, I have logged out my variables and can see when the async service finishes but there is a noticeable time difference between that and the resolve function being called.
I have since discovered the promise looks like it is waiting for Umbracos GetRemainingTimeout service before it resolves.
Can someone explain why this might be happening?
viewController.js
angular.module('umbraco')
.controller('JaywingAnalyticsHelper.ViewController', function ($scope, googleService) {
googleService.checkAuth().then(function (signedIn){
$scope.isAuthorised = signedIn;
console.log(signedIn);
});
});
googleService.js
angular.module("umbraco")
.service('googleService', function ($q) {
var clientId = 'REMOVED_FOR_PRIVACY',
scopes = ['https://www.googleapis.com/auth/analytics.readonly'],
deferred = $q.defer();
this.checkAuth = function () {
gapi.load('auth2', function () {
gapi.auth2.init().then(function () {
var googleAuth = gapi.auth2.getAuthInstance();
var signedIn = googleAuth.isSignedIn.get();
console.log(signedIn);
deferred.resolve(signedIn);
}, function(){
deferred.reject(false);
});
});
return deferred.promise;
};
});
Umbraco version - 7.5.12
Angular version - 1.1.5
After finding some time to revisit this issue I have discovered the cause of why the promise was taking so long to respond.
Most endpoints can be reached within angular by using the $http service but gapi uses its own methods to make the requests and due to the angular life-cycle it is important to call $apply which prompts angular to update any bindings or watchers.
Two links here to the documentation and another great resource:
https://code.angularjs.org/1.1.5/docs/api/ng.$rootScope.Scope#$apply
http://jimhoskins.com/2012/12/17/angularjs-and-apply.html
This was annoyingly simple and can only be blamed on my lack of angular knowledge. In my case the promise was waiting for angular to get to a certain point in its life-cycle before resolving the promise rather than updating instantly. Wrapping it in the apply function fixes this problem.
$rootScope.$apply(function(){
deferred.resolve(signedIn);
});
For those interested there was a number of steps which led me to diagnosing this issue, including:
Moving the gapi call out of the service and back into the controller
This didn't have any effect, and the promise was still taking a while to resolve.
Swapping out the gapi call for a setTimeout
Again this didn't have any effect, and the promise was still taking a while to resolve but did show that the issue wasn't directly related to gapi.
Adding multiple setTimeouts of different lengths
This was the next step as it proved that the promises were resolving at the same time even though they should be seconds apart. Two important discoveries came out of this. Interacting with the view caused the promises to resolve (some kind of life-cycle trigger) and that there is an angular version of setTimeout called $timeout
Read into why $timeout exists
This led to learning more about the angular life-cycle and the $apply function why and when to use it. Problem solved.
Although I am working with the HTTP promise object in AngularJS, I don't have a clear concept of what an HTTP promise object actually is and what the is difference between an HTTP promise object and a traditional object in AngularJS!
Would anybody explain this, please?
A Promise is a concept for asynchronous operations. Basically it represents an object that can be available at any point from now into the future.
It has three states:
Pending
Fulfilled (it completed successfully)
Rejected (it failed)
You handle the states of your Promise with two methods, then() and catch().
then() provides you with the expected object from your asynchronous call if successful, and catch() will allow you to handle the error.
A scenario where you might use a Promise is when you're making a network call, for example:
getData(): Promise<Array<string>> {
return this.http.get("http://a-test-api.com/api/getdata").toPromise();
}
You'd then use it like this:
this.getData().then(function (stringArray) {
self.data = stringArray;
});
You can find some more information on the concept here: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise
Promises are a concept. This is a question on AngularJS Promises, which are a bit different from other promises, but the concept across libraries are fundamentally the same.
What are Asynchronous Processes?
If you know what this is, skip it and read the next header, otherwise:
When you have code, it generally runs in sequential order like so:
object.method() // First,
variable = "something"; // Second,
for(var i=0; i<2; i++) {
resp = object.makeHttpRequest();
console.log(resp.data + " was #" + i);
} // Third,
console.log("Done"); // Last.
Each step is performed after the previous finishes. This can be a problem when that for loop takes a long time (imagine the HTTP request takes a long time). The request would hang an entire process until the HTTP request is finished. Very bad.
Node.js handles this by default using a callback pattern. When you call a function that is blocking (takes a long time, like reading a file on disk or making an HTTP request), you register a callback function it will call after finishing. It will apply the function with the data from the blocking function when it finishes. This allows you to run other code while that blocking function finishes.
As many Node.js developers will tell you, this code can get very messy, very fast. Instead, AngularJS (and other libraries) will return you a Promise for when the code will finish. It allows you to use a Promise Pattern.
I know what asynchronous stuff is
Promises are conceptually similar to callbacks, but much cleaner and allow a greater degree of control. Consider this:
var url = getUrlFunction();
makeHttpRequest(url, function onResponse(data) {
dataHandler(data);
console.log("done");
}, function onError(err) {
errHandler(err);
console.log("uh oh");
});
showTheUserWeAreLoading();
// Or in node.js
var url = getUrlFunction();
makeHttpRequest(url, function onResponse(err, data) {
(err) ? handleErr(err): null;
dataHandler(data);
console.log("done");
});
showTheUserWeAreLoading();
It is not very intuitive that the showTheUserWeAreLoading function will (sometimes) happen before the HTTP request if fulfilled. This leaves much to be desired when rereading your own code.
The same code, but with the makeHttpRequest returns a promise:
var url = getUrlFunction(), prom = makeHttpRequest(url);
showTheUserWeAreLoading();
prom.then(function onSuccess(data) {
dataHandler(data);
console.log("done");
}, function onError(err) {
errHandler(err);
console.log("uh oh");
});
The promise object helps track the state of the operation. You assign handlers for when the operations reaches one of two states: Fulfilled or Rejected.
It should be noted that makeHttpRequest is a stand-in for $http() in AngularJS or $.ajax in jQuery. Before the standard for promises was created in the ECMAScript standard, each library (and library version) had its own opinion on which pattern you should/can use. AngularJS previously used the .success(<function>).error(<function>) naming pattern, while jQuery used .done(<function>).fail(<function>). These naming schemes have been depreciated a very long time ago, therefore making the current difference between libraries unnoticeable (thank you ECMAScript).
The $http API is based on the deferred/promise APIs exposed by the $q service.
1.then(successCallback, [errorCallback], [notifyCallback])
2.catch(errorCallback) – shorthand for promise.then(null, errorCallback)
3.finally(callback, notifyCallback)
$q promise method
I have the following code in my react component
componentDidMount() {
ajax.call().then((data)=> {
this.setState({a:data.a});
});
}
Im making a simple call to a function that returns a promise.
My test looks like this when I am trying to test that the state gets set correctly from the resolved promise.
it('should set state of a', (done)=> {
let ajax = {
call: ()=>{}
};
spyOn(ajax, 'call').and.returnValue(Promise.resolve({a:'a'}));
let component = TestUtils.renderIntoDocument(<MyComp/>);
expect(component.state.a).toEqual('a');
});
I am pretty sure the state is actually getting set from the data returned by the promise, but the problem is I have nowhere to call the jasmine 'done()' function to tell jasmine the async operation has finished and that the assert on the state can be tested.
The React community mostly agrees that state is better managed through
"as if' it appears synchronously so it can be snapshotted. This gives us the view as a function of a single state variable and makes our code easy to test. If you're not invested in an architecture - I suggest you try FLUX like architectures and check out common libraries like redux and este.
That said, it's perfectly fine to explicitly set state of a component after AJAX like you did. I'd separate the testing into two bits:
Set a state variable to the return of the AJAX, you can test this by simply checking that the object changed.
Call render to re-render the components you're testing (rather than setStateing).
Then, you can test the rendering and the promise setting the state differently making your rendering tests easy to reason about (they're synchronous) and the AJAX testing easy (it's just objects).
In general, when you test promises with mocha you can use the dedicated promise syntax, since you're using React you're using Babel anyway so you might as well use new JavaScript features in your code:
it('does ajax', async function(){ // can also `async () =>`
var result = await ajax.call(); // call the ajax, get response, can be mocked
var state = result;
var str = React.renderToString(<MyComp prop={result} />);
// assert against the returned HTML, can also test against DOM
});
I recently did a lot of coding in AngularJS. After some time it started to feel comfortable with it and also got really productive. But unfortunately there is this one thing I don't understand:
Within my project I need to get data through $http.get and a RESTful API server. This is where I started to stumble first. After implementing promise ($q.defer etc and .then) at functions which are processing data that's necessary to continue, I thought I conquered the problem.
But in this code:
$scope.getObservationsByLocations = function() {
var promise = $q.defer();
var locationCount = 0;
angular.forEach($scope.analysisData, function(loc) { // for each location
$http.get($scope.api + 'Device?_format=json', { // get all devices
params: {
location: loc.location.id
}
}).then(function (resultDevices) {
var data = angular.fromJson(resultDevices);
promise.resolve(data);
// for each device in this location
angular.forEach(angular.fromJson(resultDevices).data.entry.map(function (dev) {
http.get($scope.api + 'Observation?_format=json', { // get all observations
params: {
device: dev.resource.id
}
}).then(function (resultObservations) {
var observations = angular.fromJson(resultObservations);
// for each obervation of that device in this location
angular.forEach(observations.data.entry.map(function(obs) {
$scope.analysisData[locationCount].observations.push({observation: obs.resource});
}));
})
}))
});
locationCount++
});
return promise.promise
};
I can't understand in which order the commands are executed. Since I use the Webstorm IDE and it's debugging feature, it would be more accurate to say I don't know why the commands are executed in an order I don't understand.
Thinking simple, everything included in the forEach have to be executed before the return is reached, because $http.get's are connected through .then's. But following the debugging information, the function iterates over locationCount++ and even returns the promise before it goes deeper (meaning after the first .then() ).
What's that all about? Did I misunderstood this part of the AngularJS concept?
Or is this just really bad practice and I should reach out for a different solution?
If the context is important/interesting: Objects are based on i.e. https://www.hl7.org/fhir/2015May/location.html#5.15.3
With JavaScript you can create only single thread applications, although e.g. here they say it is not guaranteed to be like that.
But we are talking about the real world and real browsers, so your code sample is running as a single thread (by the way the same thread is also used for rendering your CSS and HTML, at least in Firefox).
When it comes to an asynchronous call
$http.get($scope.api + 'Device?_format=json', {
it says "hey, I can do that later". And it waits with that because it must go on with the current thread.
Then, once the current task is done with return it finally can start getting the remote data.
Proof? Check this fiddle:
console.log(1);
for (var i=0;i<1000000;i++) setTimeout(function(){
console.log(2);
},0);
console.log(3);
You see the spike with the for loop? This is the moment when it registers the setTimeout asynchronous calls. Still 3 is printed before 2 because the task is not done until the 3 is printed.
The $http.get is asynchronous, so depending on (among other things) how large the fetched data is, the time it takes to 'complete' the get is variable. Hence why there is no saying in what order they will be completed
I have developed a small lib for the Dynamics CRM REST/ODATA webservice (CrmRestKit). The lib dependes on jQuery and utilizes the promise-pattern, repectivly the promise-like-pattern of jQuery.
Now I like to port this lib to bluebird and remove the jQuery dependency. But I am facing a problem because bluebird does not support the synchronous resolution of promise-objects.
Some context information:
The API of the CrmRestKit excepts an optional parameter that defines if the web-service call should be performed in sync or async mode:
CrmRestKit.Create( 'Account', { Name: "foobar" }, false ).then( function ( data ) {
....
} );
When you pass "true" or omit the last parameter, will the method created the record in sync. mode.
Sometimes it is necessary to perform a operation in sync-mode, for instance you can write JavaScript code for Dynamics CRM that is involed for the save-event of an form and in this event-handler you need to perform sync-operation for validation (e.g. validate that a certain number of child-records exist, in case the right number of records exist, cancel the save-operation and show an error message).
My problem now is the following: bluebird does not support the resolution in sync-mode. For instance when I do the following, the "then" handler is invoked in async fashion:
function print( text ){
console.log( 'print -> %s', text );
return text;
}
///
/// 'Promise.cast' cast the given value to a trusted promise.
///
function getSomeTextSimpleCast( opt_text ){
var text = opt_text || 'Some fancy text-value';
return Promise.cast( text );
}
getSomeTextSimpleCast('first').then(print);
print('second');
The output is the following:
print -> second
print -> first
I would expect that the "second" appears after the "first" because the promise is already resolved with an value. So I would assume that an then-event-handler is immediately invoked when applied on an already resolved promise-object.
When I do the same (use then on an already resolved promise) with jQuery I will have my expected result:
function jQueryResolved( opt_text ){
var text = opt_text || 'jQuery-Test Value',
dfd = new $.Deferred();
dfd.resolve(text);
// return an already resolved promise
return dfd.promise();
}
jQueryResolved('third').then(print);
print('fourth');
This will generate the following output:
print -> third
print -> fourth
Is there a way to make bluebird work in the same fashion?
Update:
The provided code was just to illustrate the problem. The idea of the lib is: Regardless of the execution-mode (sync, async) the caller will always deal with an promise-object.
Regarding "... asking the user... doesn't seems to make any sense": When you provide two methods "CreateAsync" and "CreateSync" it is also up to the user to decide how the operation is executed.
Anyway with the current implementation the default behavior (last parameter is optional) is a async execution. So 99% of the code requires a promise-object, the optional parameter is only use for the 1% cases where you simply need a sync execution. Furthermore I developed to lib for myself and I use in 99,9999% of the case the async mode but I thought it is nice to have the option to go the sync-road as you like.
But I thinks I got the point an sync method should simply return the value. For the next release (3.0) I will implement "CreateSync" and "CreateAsync".
Thanks for your input.
Update-2
My intension for the optional parameter was to ensure a consistend behavior AND prevent logic error. Assume your as a consumer of my methode "GetCurrentUserRoles" that uses lib. So the method will alway return an promise, that means you have to use the "then" method to execute code that depends on the result. So when some writes code like this, I agree it is totally wrong:
var currentUserRoels = null;
GetCurrentUserRoles().then(function(roles){
currentUserRoels = roles;
});
if( currentUserRoels.indexOf('foobar') === -1 ){
// ...
}
I agree that this code will break when the method "GetCurrentUserRoles" changes from sync to async.
But I understand that this I not a good design, because the consumer should now that he deals with an async method.
Short version: I get why you want to do that, but the answer is no.
I think the underlying question being asked is whether a completed promise should immediately run a callback, if the promise has already completed. I can think of a lot of reasons that this might happen - for example, an asynchronous save procedure that only saves data if changes were made. It may be able to detect changes from the client side in a synchronous fashion without having to go through an external resource, but if changes are detected then and only then would an asynchronous operation be required.
In other environments that have asynchronous calls, the pattern seems to be that the developer is responsible for understanding that their work might complete immediately (for example, .NET framework's implementation of the async pattern accomodates this). This is not a design problem of the framework, it's the way it's implemented.
JavaScript's developers (and many of the commenters above) seem to have a different point of view on this, insisting that if something might be asynchronous, it must always be asynchronous. Whether this is "right" or not is immaterial - according to the specification I found at https://promisesaplus.com/, item 2.2.4 states that basically no callbacks can be called until you are out of what I'll refer to as "script code" or "user code"; that is, the specification says clearly that even if the promise is completed you can't invoke the callback immediately. I've checked a few other places and they either say nothing on the topic or agree with the original source. I don't know if https://promisesaplus.com/ could be considered a definitive source of information in this regard, but no other sources that I saw disagreed with it and it seems to be the most complete.
This limitation is somewhat arbitrary and I frankly prefer the .NET perspective on this one. I'll leave it up to others to decide if they consider it "bad code" to do something that might or might not be synchronous in a way that looks asynchronous.
Your actual question is whether or not Bluebird can be configured to do the non-JavaScript behavior. Performance-wise there may be a minor benefit to doing so, and in JavaScript anything's possible if you try hard enough, but as the Promise object becomes more ubiquitous across platforms you will see a shift to using it as a native component instead of custom written polyfills or libraries. As such, whatever the answer is today, reworking a promise in Bluebird is likely to cause you problems in the future, and your code should probably not be written to depend on or provide immediate resolution of a promise.
You might think this is a problem, because there's no way to have
getSomeText('first').then(print);
print('second');
and to have getSomeText "first" printed before "second" when the resolution is synchronous.
But I think you have a logic problem.
If your getSomeText function may be synchronous or asynchronous, depending on the context, then it shouldn't impact the order of execution. You use promises to ensure it's always the same. Having a variable order of execution would likely become a bug in your application.
Use
getSomeText('first') // may be synchronous using cast or asynchronous with ajax
.then(print)
.then(function(){ print('second') });
In both cases (synchronous with cast or asynchronous resolution), you'll have the correct execution order.
Note that having a function being sometimes synchronous and sometimes not isn't a weird or unlikely case (think about cache handling, or pooling). You just have to suppose it's asynchronous, and all will be always fine.
But asking the user of the API to precise with a boolean argument if he wants the operation to be asynchronous doesn't seem to make any sense if you don't leave the realm of JavaScript (i.e. if you don't use some native code).
The point of promises is to make asynchronous code easier, i.e. closer to what you feel when using synchronous code.
You're using synchronous code. Don't make it more complicated.
function print( text ){
console.log( 'print -> %s', text );
return text;
}
function getSomeTextSimpleCast( opt_text ){
var text = opt_text || 'Some fancy text-value';
return text;
}
print(getSomeTextSimpleCast('first'));
print('second');
And that should be the end of it.
If you want to keep the same asynchronous interface even though your code is synchronous, then you have to do it all the way.
getSomeTextSimpleCast('first')
.then(print)
.then(function() { print('second'); });
then gets your code out of the normal execution flow, because it's supposed to be asynchronous. Bluebird does it the right way there. A simple explanation of what it does:
function then(fn) {
setTimeout(fn, 0);
}
Note that bluebird doesn't really do that, it's just to give you a simple example.
Try it!
then(function() {
console.log('first');
});
console.log('second');
This will output the following:
second
first
There are some good answers here already, but to sum up the crux of the matter very succinctly:
Having a promise (or other async API) that is sometimes asynchronous and sometimes synchronous is a bad thing.
You may think it's fine because the initial call to your API takes a boolean to switch off between sync/async. But what if that's buried in some wrapper code and the person using that code doesn't know about these shenanigans? They've just wound up with some unpreditable behavior through no fault of their own.
The bottom line: Don't try to do this. If you want synchronous behavior, don't return a promise.
With that, I'll leave you with this quotation from You Don't Know JS:
Another trust issue is being called "too early." In application-specific terms, this may actually involve being called before some critical task is complete. But more generally, the problem is evident in utilities that can either invoke the callback you provide now (synchronously), or later (asynchronously).
This nondeterminism around the sync-or-async behavior is almost always going to lead to very difficult to track down bugs. In some circles, the fictional insanity-inducing monster named Zalgo is used to describe the sync/async nightmares. "Don't release Zalgo!" is a common cry, and it leads to very sound advice: always invoke callbacks asynchronously, even if that's "right away" on the next turn of the event loop, so that all callbacks are predictably async.
Note: For more information on Zalgo, see Oren Golan's "Don't Release Zalgo!" (https://github.com/oren/oren.github.io/blob/master/posts/zalgo.md) and Isaac Z. Schlueter's "Designing APIs for Asynchrony" (http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony).
Consider:
function result(data) {
console.log( a );
}
var a = 0;
ajax( "..pre-cached-url..", result );
a++;`
Will this code print 0 (sync callback invocation) or 1 (async callback invocation)? Depends... on the conditions.
You can see just how quickly the unpredictability of Zalgo can threaten any JS program. So the silly-sounding "never release Zalgo" is actually incredibly common and solid advice. Always be asyncing.
What about this case, also CrmFetchKit related which in latest version uses Bluebird. I have upgraded from version 1.9 that was based on jQuery. Still the old app code that uses CrmFetchKit has methods the prototypes of which I can't or won't change.
Existing App Code
CrmFetchKit.FetchWithPaginationSortingFiltering(query.join('')).then(
function (results, totalRecordCount) {
queryResult = results;
opportunities.TotalRecords = totalRecordCount;
done();
},
function err(e) {
done.fail(e);
}
);
Old CrmFetchKit implementation (a custom version of fetch())
function fetchWithPaginationSortingFiltering(fetchxml) {
var performanceIndicator_StartTime = new Date();
var dfd = $.Deferred();
fetchMore(fetchxml, true)
.then(function (result) {
LogTimeIfNeeded(performanceIndicator_StartTime, fetchxml);
dfd.resolve(result.entities, result.totalRecordCount);
})
.fail(dfd.reject);
return dfd.promise();
}
New CrmFetchKit implementation
function fetch(fetchxml) {
return fetchMore(fetchxml).then(function (result) {
return result.entities;
});
}
My problem is that the old version had the dfd.resolve(...) where I was able to pass any number of params that I need.
The new implementation just returns, the parent seems to call the callback, I can't call it directly.
I went and made a custom version of the fetch() in the new implementation
function fetchWithPaginationSortingFiltering(fetchxml) {
var thePromise = fetchMore(fetchxml).then(function (result) {
thePromise._fulfillmentHandler0(result.entities, result.totalRecordCount);
return thePromise.cancel();
//thePromise.throw();
});
return thePromise;
}
But the problem is that the callback gets called two times, once when I do it explicitly and second by the framework but it passes it one parameter only. To trick it and "tell" not to call anything because I do it explicitly I try to call .cancel() but it is ignored. I understood why but still how do you do the "dfd.resolve(result.entities, result.totalRecordCount);" in the new version with out having to changes prototypes in the app that uses this library ?
You can in fact do this, yes.
Modify the bluebird.js file (for npm: node_modules/bluebird/js/release/bluebird.js), with the following change:
[...]
target._attachExtraTrace(value);
handler = didReject;
}
- async.invoke(settler, target, {
+ settler.call(target, {
handler: domain === null ? handler
: (typeof handler === "function" &&
[...]
For more info, see here: https://github.com/stacktracejs/stacktrace.js/issues/188