2017-10-11 2 views
0

Ce script fait partie d'une application Web qui permet aux utilisateurs d'afficher des informations sur les séismes sur une carte Google Map. Le script charge les google maps de manière asynchrone et crée un Knockout ViewModel. L'utilisateur peut choisir quels tremblements de terre afficher via certains menus déroulants, qui sont liés à Knockout Observables (self.curFeedType et self.curFeedTimeHorizon). L'application affichera alors une liste de titres de tremblement de terre à côté de la carte, filtrant ceux qui ne sont pas dans les limites de la fenêtre de la carte. Les données sismiques sont chargées via une requête AJAX de USGS.gov. Je souhaite que la liste des titres de tremblement de terre affichés se mette à jour immédiatement après la mise à jour des types de flux --- cela ne fonctionne pas actuellement. Je crois que c'est un problème asynchrone --- quand j'utilise le débogueur cela fonctionne, mais pas vice versa.La vue Knockout ne se met pas à jour avec geoJSON Filtré par des limites de Google Map

Je sais que la plupart des fonctionnalités fonctionnent car il existe un programme d'écoute distinct qui met à jour l'interface utilisateur lorsque les limites de la carte changent. À l'heure actuelle, toutefois, la liste des titres de tremblement de terre ne sera pas mise à jour avant que le flux ne soit modifié et que la carte ne soit déplacée.

function ControlViewModel() { 
    var self = this; 
    self.map = null; 
    self.loadedQuakes = []; 
    self.visibleQuakes = ko.observableArray(); 
    // types of available earthquake feeds from USGS.gov 
    self.feedTypes = ["significant", "4.5", "2.5", "1.0", "all"]; 
    self.feedTimeHorizons = ["hour", "day", "week", "month"]; 
    // use significant and week as default feed when app loads 
    self.curFeedType = ko.observable("significant"); 
    self.curFeedTimeHorizon = ko.observable("week"); 

    function setVisibleQuakes (bounds, quakesToFilter) { 
    self.visibleQuakes(quakesToFilter.filter(quake => { 
     return bounds.contains(quake.latLon); 
    })); 
    } 

    self.updateVisibleQuakes = function(bounds, loadedQuakes) { 
    loadedQuakes ? setVisibleQuakes(bounds, loadedQuakes) : 
    setVisibleQuakes(bounds, self.loadedQuakes); 
    } 

    // Generate a url for the desired earthquake feed 
    self.generateFeedUrl = function() { 
    let baseFeedUrl = `https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/${self.curFeedType()}_${self.curFeedTimeHorizon()}.geojson`; 
    return baseFeedUrl; 
    } 

    // load quakes from USGS and create new model objects 
    async function getQuakeFeed() { 
    let loadedQuakes = []; 
    $.getJSON(self.generateFeedUrl(), function(data) { 
     for (var i = 0; i < data.features.length; i++) { 
     loadedQuakes.push(new earthQuakeModel(data.features[i])); 
     }; 
    }); 
    return loadedQuakes; 
    } 

    self.updateQuakeFeed = async function() { 
    if (self.map) { 
     let loadedQuakes = getQuakeFeed(); 
     loadedQuakes.then(result => { 
     self.updateVisibleQuakes(self.map.getBounds(), result); 
     self.loadedQuakes = result; 
     }); 
    } 
    } 

    // update the feed when either select menu changes 
    self.curFeedType.subscribe(self.updateQuakeFeed, null); 
    self.curFeedTimeHorizon.subscribe(self.updateQuakeFeed, null); 
    // call for inital setup 
    self.updateQuakeFeed(); 
} 

var controlViewModel = new ControlViewModel(); 

// create a new Google Map 
function initMap() { 
    let map = new google.maps.Map(document.getElementById('map_container'), { 
    center: {lat: 0, lng: 0}, 
    zoom: 3 
    }); 
    console.log(map); 
    controlViewModel.map = map; 
    // listener to let UI know that map bounds have changed 
    map.addListener('idle', function() { 
    let bounds = map.getBounds(); 
    controlViewModel.updateVisibleQuakes(bounds, null); 
    }); 
} 

ko.applyBindings(controlViewModel); 

Répondre

0

Depuis que j'ai posé cette question, j'ai trouvé la réponse.

La fonction suivante fait une requête AJAX:

// load quakes from USGS and create new model objects 
    async function getQuakeFeed() { 
    let loadedQuakes = []; 
    $.getJSON(self.generateFeedUrl(), function(data) { 
     for (var i = 0; i < data.features.length; i++) { 
     loadedQuakes.push(new earthQuakeModel(data.features[i])); 
     }; 
    }); 
    return loadedQuakes; 
    } 

Même si cette fonction a été déclarée comme une fonction async, enroulant implicitement la valeur de retour dans une promesse, il n'attend pas avant immédiatement retourner le tableau vide, avant que la requête AJAX soit terminée. Ainsi, la promesse est immédiatement "remplie", ce qui provoque l'échec du reste de la chaîne self.updateQuakeFeed().

Je corrige ce problème en encapsulant immédiatement la demande AJAX dans une promesse et d'attendre qu'il soit accompli avant de poursuivre:

async function getQuakeFeed() { 
    return $.getJSON(self.generateFeedUrl()); 
    } 

    self.populateQuakeModel = async function() { 
    let feedResults = await getQuakeFeed(); 
    let newQuakes = []; 
    feedResults.features.forEach(feature => { 
     newQuakes.push(new earthQuakeModel(feature)); 
    }); 
    return newQuakes; 
    } 

    self.updateQuakeFeed = async function() { 
    if (self.map) { 
     let newQuakes = await self.populateQuakeModel(); 
     self.loadedQuakes = newQuakes; 
     self.updateVisibleQuakes(self.map.getBounds()); 
    } 
    } 

Le guide suivant de Google a été utile pour comprendre cela: Promises "Compatibility with other libraries" qui traite la valeur de wrapping immédiat de jQuery's Deferred --- the wrapper of return type from jQuery.ajax --- dans un objet Promesse ES6 réel.