背景和目標
我有兩個fetch/then鏈來構建事件處理程式所需的元素。
第一個鏈加載資料以構建<select>元素。默認情況下會選擇某些選項。中的函式then不回傳任何內容。它只<select>在 DOM 中創建元素。
fetch("data/select.json")
.then(response => response.json())
.then(data => {
// populate select options
});
第二個鏈加載資料以使用Chartist繪制多個圖表。
fetch("data/chartist.json")
.then(response => response.json())
.then(data => Object.keys(data).forEach(x =>
// draw charts using Chartist
new Chartist.Line(`#${x}`, { "series": data[x] });
));
最后,我需要為每個圖表物件添加一個事件處理程式(在"created"事件上使用Chartist 的on方法)。處理函式需要獲取在第一個/鏈<select>中構建的元素中選擇的值。由于每次(重新)繪制圖表時都會發出(例如圖表創建、視窗調整大小等),因此用戶可能同時選擇了與默認值不同的值。因此,我不能只使用包含默認值的靜態陣列。相反,我從DOM 中元素的屬性中獲取選定的值(即),所以我需要等待fetchthen"created"<select>selectElement.selectedOptions<select>元素準備好。
我試過的
目前,我將事件處理程式添加到第二個fetch/then鏈中的每個圖表物件。<select>但是,如果在元素準備好之前執行獲取所選選項的代碼,則會失敗。
fetch("data/chartist.json")
.then(response => response.json())
.then(data => Object.keys(data).forEach(x => {
// draw charts using Chartist
const chart = new Chartist.Line(`#${x}`, { "series": data[x] });
// add an event handler to the charts
chart.on("created", () => {
// get the values selected in the <select> and use it
});
}));
我也嘗試嵌套第二個fetch/then鏈嵌套在第一個中或添加超時。這不是最優的,因為它不異步獲取資料會浪費時間。
問題
我想最干凈的解決方案是從第二個fetch/then鏈中提取事件處理程式,讓fetch/then鏈異步運行,并且只在添加事件處理程式之前等待。這是一個好方法嗎?
如果是,我認為我需要做出thens 回傳承諾并等待(例如使用Promise.all)。一個承諾應該回傳圖表物件而不會丟失任何事件(特別是"created"在圖表創建時發出的第一個事件),因此我可以在第二個fetch/之外提取事件處理程式then鏈之外。
我閱讀了許多關于 SO 的相關問題(例如關于異步編程和承諾),以及關于關閉此類問題的元執行緒。我知道這個理論已經被徹底解釋了好幾次,但是盡管仔細閱讀(這里是初學者),我還是沒有設法對我的問題實施一個干凈的解決方案。任何提示將不勝感激。
uj5u.com熱心網友回復:
您遇到了所謂的競爭條件。承諾的解決順序是不確定的。
您可以使用以下方法等待兩個 fetch 請求解決Promise.all():
Promise.all([
fetch('data/select.json').then(r => r.json()),
fetch('data/chartist.json').then(r => r.json())
]).then(results => {
// populate select options from results[0]
Object.keys(results[1]).forEach(x => {
let chart = new Chartist.Line(`#${x}`, { "series": results[1][x] });
chart.on('created', event => {
// get the values selected in the <select> and use it
});
});
});
但與嵌套 fetches 一樣,此方法會為您的應用程式增加不必要的延遲。您的選擇元素資料可能會在圖表資料之前收到,但您不必提前更新 UI,您將不得不等待第二個承諾解決。此外,兩個請求都必須成功,否則您的代碼將無法運行。您可以Promise.allSettled()手動使用和處理任何錯誤情況,但您仍然必須等待兩個請求都完成,然后才能執行代碼。
處理此問題的更好方法是通過擴展EventTarget并在填充選擇元素時調度CustomEvent來利用 Web 的事件驅動特性。
因為我們知道我們的自定義populated事件有可能在 Chartist 獲取解決之前觸發,所以我們還需要使用幾個布林值來跟蹤狀態。如果我們錯過了事件,只需配置圖表,否則附加一個EventListener并等待,但前提是還沒有偵聽器排隊。
在一個新檔案中:
export class YourController extends EventTarget {
constructor() {
super();
}
populated = false;
async getData(path) {
return fetch(path).then(res => res.json());
}
async getSelectOptions() {
let data = await this.getData('data/select.json');
// populate select options here
this.populated = true;
this.dispatchEvent(new CustomEvent('populated'));
}
async getChartData() {
let data = await this.getData('data/chartist.json');
Object.keys(data).forEach(x => this.createChart(x, data[x]));
}
createChart(id, data) {
let chart = new Chartist.line(`#${id}`, { series:data });
chart.on('created', event => this.onCreated(chart));
}
onCreated(chart) {
if (this.populated) { this.configure(chart); }
else if (chart.waiting) { return; } chart.waiting = true;
this.addEventListener('populated', event => this.configure(chart), { once:true });
}
configure(chart) {
// get and use selected options here
chart.waiting = false;
this.dispatchEvent(new CustomEvent('configured', { detail:chart }));
}
}
在您的應用中:
import { YourController } from './your-controller.js'
const c = new YourController();
c.addEventListener('configured', event => {
console.log('configured:', event.detail)
});
c.getSelectOptions();
c.getChartData();
設定{ once:true }選項 inaddEventListener()將在populated事件第一次觸發后自動洗掉偵聽器。由于該created事件可以觸發多次,這將防止無休止的呼叫堆疊.configure(chart)堆積。我還更新了腳本以防止created在之前多次觸發時添加額外的偵聽器populated。
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/471681.html
標籤:javascript 异步 承诺 拿来 chartist.js
