Ember计算属性排序异常求助:排序结果出现错位
Hey Chris, let's figure out why that one vineyard is ending up at the bottom when it should be second—this is a mix of async timing and string sorting quirks that's throwing things off. Let's break down the problems and fix them step by step.
What's Causing the Problem?
1. Async API Calls Are Messing Up Sorting Timing
Your distanceFrom action loops through each vineyard and fires off an asynchronous Google Maps Distance Matrix request. The issue is that Ember's computed property sortedVineyards will try to sort the list before all those async callbacks finish setting the distanceTo values. So when the sort runs, some items might not have their distanceTo set yet, and others might get updated later without triggering a re-sort properly.
2. You're Sorting Strings, Not Numbers
Right now, you're storing distanceTo as a formatted string like "2 mi" or "10 mi". When Ember sorts strings, it compares them character by character—so "10 mi" will come before "2 mi" because "1" is less than "2" in string comparison. That's why your second-place vineyard is getting pushed to the end!
3. Incorrect self Context Inside the Async Callback
In your then handler for the Google Maps API, you're redefining var self = this;—but inside that arrow function, this already refers to the component context. Redefining it here breaks access to your component's properties later on.
How to Fix It
Let's adjust your code to address all three issues:
Step 1: Store Distance as a Numeric Value
Instead of saving the formatted string, save the raw numeric distance (in miles, since you're using imperial units) so sorting works as expected.
Step 2: Fix Async Timing & Context
Use arrow functions to preserve context, and make sure that when distanceTo updates, the computed property re-sorts the list correctly. We'll also clean up the async flow to avoid context confusion.
Modified Component Code
import Component from '@ember/component'; import { inject as service } from '@ember/service'; import { computed } from '@ember/object'; export default Component.extend({ googleMapsApi: service(), geolocation: service(), sortDefinition: ['distanceTo:asc'], // Explicitly set ascending sort (default is asc, but clear is better) sortedVineyards: computed.sort('model', 'sortDefinition'), didInsertElement() { this._super(...arguments); // Call super first—you had this inside the forEach before, which was wrong! this.send('distanceFrom'); }, actions: { async distanceFrom() { const userLocation = this.get('userLocation'); const [userLocationLat, userLocationLon] = userLocation; const userLocationFormat = `${userLocationLat},${userLocationLon}`; const model = this.get('model'); const google = await this.get('googleMapsApi.google'); const distanceMatrixService = new google.maps.DistanceMatrixService(); // Create a helper function to calculate distance for a single item const calculateDistance = async (item) => { const endLocation = item.get('location'); return new Promise((resolve) => { distanceMatrixService.getDistanceMatrix({ origins: [userLocationFormat], destinations: [endLocation], travelMode: google.maps.TravelMode.DRIVING, unitSystem: google.maps.UnitSystem.IMPERIAL, avoidHighways: false, avoidTolls: false }, (response, status) => { if (status !== google.maps.DistanceMatrixStatus.OK) { resolve(null); return; } const element = response.rows[0].elements[0]; if (element.status === "ZERO_RESULTS") { resolve(null); return; } // Save the numeric value (in miles) instead of the string const distanceValue = element.distance.value / 1609.34; // Convert meters to miles item.set('distanceTo', distanceValue); resolve(distanceValue); }); }); }; // Wait for all distance calculations to finish await Promise.all(model.map(item => calculateDistance(item))); // Once all distances are set, the computed property will automatically re-sort } } });
Key Changes Explained
- Async/Await: Using
async/awaitmakes the async flow easier to follow, andPromise.allensures we wait for all distance calculations to complete before moving on. - Numeric Distance: We convert the raw meter value from Google Maps to miles (divide by 1609.34) and store that number in
distanceTo—so sorting is based on actual numeric values, not strings. - Fixed Context: Removed the incorrect
selfredefinitions and used arrow functions where needed to keep the component context intact. - Proper
_superCall: Movedthis._super(...arguments)to the top ofdidInsertElementwhere it belongs (you had it inside the forEach loop before, which was incorrect). - Explicit Sort Direction: Added
:asctosortDefinitionto make the sort direction clear.
Why This Works
Now, when all the distance values are set (as numbers), the sortedVineyards computed property will automatically re-run and sort the list correctly. No more string comparison weirdness, and we ensure all async calls finish before the sort is finalized.
内容的提问来源于stack exchange,提问作者Chris Johnson

