In the effort to modernize the CesiumJS code base, we’re finally planning on removing our outdated copy of when.js and moving to native promises.
This will need to be a breaking change. As of release version 1.91, you will need to do the following to ensure your code is working properly.
Remove any references to Cesium.when in your codebase, replacing them with native Promises, (eg. Promise.resolve)
when.defer should be replaced by creating a new Promise and passing in a callback to the constructor.
when.join should be replaced by Promise.all
In promise chains which originate from the Cesium API, switch to using the Promise API
otherwise changes to catch
always changes to finally
If you’re using async/await you can continue to do so without changes, as they will work the same with native promises.
Since Cesium is currently using an older version of when.js, technically there will be a change in when promises execute. While before resolved promises would run immediately, native promises will not run synchronously and will instead run at the end of a frame once resolved. However this implementation detail should not have performance implications within Cesium and is unlikely to affect your apps.
Please leave a reply here if you have any questions, tips, or tricks around this change.
As a vaguely related side question: Is there any particular reason why Cesium isn’t using semantic versioning? Obviously the last few years have shown that Cesium can get along fine without semver But it is kinda weird as a user to read a phrase like “next minor release will have breaking changes”.
I know that Cesium has had a lot of breaking changes over the years, and under semver that would have resulted in lots (dozens?) of required major version bumps.
TypeScript would be another example of a large project that doesn’t use semver - every TS release can have breaking changes.
While we haven’t committed one way or the other yet, semver is under consideration for the near future. It’s my understanding that historically we chose not to use it for exactly the reason you said-- Since we had breaking changes in most of our releases, it would have resulted in many major version bumps. However, the project may be at a point where we have fewer breaking changes and semver will help make upgrading clearer.
A quick update on this-- Since this is a fairly substantial breaking change, we’ve decided on delaying the change until the next release, 1.92. We know not everyone follows the community forum, so we’ll add a bullet to CHANGES.md to warn of the change in the subsequent release.
It will affect you if you are handling any promises returned by the Cesium API, as they will have been when-style promises and will now be native-style promises. These are common for handling errors when loading data sources and 3D Tilesets. To know if you are, you can search for .otherwise or .always and replace them with .catch and .finally as described in the top comment.
I’m just curious, I remember discussing this with Matt in an issue several years ago, and at the time he claimed that library promises significantly outperformed native in a few key scenarios – he had benchmarked an all-native build, and performance was measurably worse. Have the facts on the ground changed? Have modern browsers fixed whatever implementation detail made them worse for your use-case? (I’m thrilled to get rid of the nonstandard library, just from a developer ergonomics perspective, but it is an about-face from the last time the idea was raised.)
Matt did another round of performance in his initial PR in 2020. With native promises, Cesium is running at the same level of performance. I’m honestly not sure what changed in the implementation between a few years ago and 2020, but we’re still seeing similar performance in the latest PR.
Hi ks_sc, historically cesiumjs has chosen not to use semver and not bump the major version due to frequent breaking changes, especially early in development. However, we are currently considering moving to semver and to bump major version in cases like this. We’re in the process of confirming this decision since it will likely have impacts among government users. So we’re thinking about it, but I can’t promise a major version increment for this next release.
Hi Gabby, Thanks for the info. I cannot find any occurrence of .always or .otherwise in our app (I’m not the original developer of it), so I assume we will be ok? Anyway I recently switched to loading Cesium in our app from cdn, so I’ll be able to do a quick compatibility check by updating the urls.
To work with MSL values in Cesium we’re working with EarthGravityModel1996.js and the WW15MGH.DAC file. There is a function ( getHeightData() ) that we’re trying to convert to use a native Promise. Could you help walk us through this? Here’s the function as it is now:
function getHeightData(model) {
if (!Cesium.defined(model.data)) {
model.data = Cesium.Resource.fetchArrayBuffer({"url":EGM96GridFileUrl});
}
return Cesium.when(model.data, function(data) {
if (!(model.data instanceof Int16Array)) {
// Data file is big-endian, all relevant platforms are little endian, so swap the byte order.
var byteView = new Uint8Array(data);
for (var k = 0; k < byteView.length; k += 2) {
var tmp = byteView[k];
byteView[k] = byteView[k + 1];
byteView[k + 1] = tmp;
}
try {
model.data = new Int16Array(data);
}
catch (ex) {
console.log("getHeightData Error: " + ex.toString());
}
}
return model.data;
});
}
function getHeightData(model) {
if (!Cesium.defined(model.data)) {
// Return a promise which resolves to the new model data
return Cesium.Resource.fetchArrayBuffer({"url":EGM96GridFileUrl}).then(function (data) {
if (!(data instanceof Int16Array)) {
// Data file is big-endian, all relevant platforms are little endian, so swap the byte order.
var byteView = new Uint8Array(data);
for (var k = 0; k < byteView.length; k += 2) {
var tmp = byteView[k];
byteView[k] = byteView[k + 1];
byteView[k + 1] = tmp;
}
try {
model.data = new Int16Array(data);
}
catch (ex) {
console.log("getHeightData Error: " + ex.toString());
}
} else {
model.data = data;
}
return model.data;
});
}
// Otherwise return a promise that immediately resolves to the existing data
return Promise.resolve(model.data);
}
The getHeightData function (in above post) is called many times. The way it was previously written only downloads the file the first time the function is called. But the new approach with native promise allows the file to be downloaded many times (potentially hundreds) because model.data remains undefined until the promise resolves.
So far I haven’t come up with a clean way to write the code to prevent this from happening. How would you suggest changing this so that the file is only downloaded once?
From your previous example, it looks like its OK to set model.data to a promise or array. In my above example, I have treated model.data as an array. We can rewrite the function so that model.data is assigned a promise instead.
function getHeightData(model) {
if (!Cesium.defined(model.data)) {
// Assign model.data to a promise which resolves to the data
model.data = Cesium.Resource.fetchArrayBuffer({"url":EGM96GridFileUrl}).then(function (data) {
if (!(data instanceof Int16Array)) {
// Data file is big-endian, all relevant platforms are little endian, so swap the byte order.
var byteView = new Uint8Array(data);
for (var k = 0; k < byteView.length; k += 2) {
var tmp = byteView[k];
byteView[k] = byteView[k + 1];
byteView[k + 1] = tmp;
}
try {
return new Int16Array(data);
}
catch (ex) {
console.log("getHeightData Error: " + ex.toString());
}
}
return data;
});
}
// Otherwise return the existing promise
return model.data;
}