如何等待eventListener触发后再执行Promise后续链式调用
问题描述
开发中需要等待动态渲染选择列表的change事件监听器捕获到用户选择操作后,再执行fetch Promise链的后续异步逻辑,最终把用户选中的值传入Promise回调,供后续接口请求使用。
现有实现逻辑为:通过fetch拉取接口数据后渲染下拉选项列表,但要求在用户触发选择前,不执行Promise链中后续的.then(resp => resp.json())逻辑。
问题复现代码
页面HTML代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Dynamic List Test</title> </head> <body> <div class="container"> <!-- App entry point --> </div> <script type="text/javascript" src="app.js"></script> </body> </html>
业务JS代码
const testUrl = 'https://hub.dummyapis.com/employee?noofRecords=10&idStarts=1001'; const result = fetch(testUrl, { method: 'GET', }) .then(resp => resp.json()) .then(data => { // 创建选择下拉框 const mainEl = document.querySelector('.container'); let selectList = document.createElement('select'); selectList.id = 'selectorList'; mainEl.appendChild(selectList); // 组装选项数据 let nameArray = []; const nameList = data; nameArray.push(nameList); console.log(nameArray[0]); for (let last in nameArray[0]) { let surname = nameArray[0][last].lastName; let option = document.createElement('option'); option.value = surname; option.text = surname; selectList.appendChild(option); } let selectorChoice = ''; // 绑定下拉框变更事件 selectList.addEventListener('change', e => { selectorChoice = e.target.value; console.log(selectorChoice); return selectorChoice; }); }) .then(resp => resp.json()) // 期望等待事件监听器触发后再执行这一步 .catch(error => console.log('Error:', error)); result.then(resp => { console.log(resp); });
报错现象
代码运行后控制台抛出错误:TypeError: Cannot read properties of undefined (reading 'json'),无法正确获取用户选中的选项值,控制台完整输出如下:
(10) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]0: {id: 1001, imageUrl: 'https://hub.dummyapis.com/Image?text=CA&height=120&width=120', firstName: 'Colt', lastName: 'Altenwerth', email: 'Colt.Altenwerth@dummyapis.com', …}1: {id: 1002, imageUrl: 'https://hub.dummyapis.com/Image?text=HW&height=120&width=120', firstName: 'Helena', lastName: 'Ward', email: 'Helena.Ward@dummyapis.com', …}2: {id: 1003, imageUrl: 'https://hub.dummyapis.com/Image?text=LC&height=120&width=120', firstName: 'Liliana', lastName: 'Connelly', email: 'Liliana.Connelly@dummyapis.com', …}3: {id: 1004, imageUrl: 'https://hub.dummyapis.com/Image?text=MH&height=120&width=120', firstName: 'Myah', lastName: 'Hane', email: 'Myah.Hane@dummyapis.com', …}4: {id: 1005, imageUrl: 'https://hub.dummyapis.com/Image?text=MS&height=120&width=120', firstName: 'Mauricio', lastName: 'Stracke', email: 'Mauricio.Stracke@dummyapis.com', …}5: {id: 1006, imageUrl: 'https://hub.dummyapis.com/Image?text=CS&height=120&width=120', firstName: 'Candice', lastName: 'Sipes', email: 'Candice.Sipes@dummyapis.com', …}6: {id: 1007, imageUrl: 'https://hub.dummyapis.com/Image?text=EA&height=120&width=120', firstName: 'Evangeline', lastName: 'Aufderhar', email: 'Evangeline.Aufderhar@dummyapis.com', …}7: {id: 1008, imageUrl: 'https://hub.dummyapis.com/Image?text=RR&height=120&width=120', firstName: 'Rosetta', lastName: 'Rodriguez', email: 'Rosetta.Rodriguez@dummyapis.com', …}8: {id: 1009, imageUrl: 'https://hub.dummyapis.com/Image?text=TD&height=120&width=120', firstName: 'Tina', lastName: "D'Amore", email: "Tina.D'Amore@dummyapis.com", …}9: {id: 1010, imageUrl: 'https://hub.dummyapis.com/Image?text=PS&height=120&width=120', firstName: 'Pearline', lastName: 'Sawayn', email: 'Pearline.Sawayn@dummyapis.com', …}length: 10[[Prototype]]: Array(0) app.js:37 Error: TypeError: Cannot read properties of undefined (reading 'json') at app.js:36:22 app.js:40 undefined app.js:32 Hane
问题原因
- 事件监听器的
return只会在事件触发时执行,不会把返回值传给外层Promise链,渲染完下拉框的.then回调没有任何返回值,所以下一层.then接收到的参数是undefined,调用.json()自然报错。 - DOM事件是异步触发的,原生Promise链不会自动等待用户交互事件触发,需要手动封装Promise把事件回调和Promise链关联起来。
- 最后多余的
.then(resp => resp.json())完全没有必要:第一次fetch拿到的响应已经在最开始转成json了,用户选值本身是字符串,不需要再调用json解析方法。
修复方案
在渲染完下拉框后,返回一个新的Promise,在change事件触发时调用resolve把用户选中的值传出去,删掉多余的json解析步骤即可。
修复后完整JS代码
const testUrl = 'https://hub.dummyapis.com/employee?noofRecords=10&idStarts=1001'; const result = fetch(testUrl, { method: 'GET', }) .then(resp => resp.json()) .then(data => { // 创建选择下拉框 const mainEl = document.querySelector('.container'); let selectList = document.createElement('select'); selectList.id = 'selectorList'; mainEl.appendChild(selectList); // 组装选项数据 for (let item of data) { let surname = item.lastName; let option = document.createElement('option'); option.value = surname; option.text = surname; selectList.appendChild(option); } // 返回新Promise等待用户选择 return new Promise(resolve => { selectList.addEventListener('change', e => { const selectorChoice = e.target.value; console.log(selectorChoice); resolve(selectorChoice); }, { once: true }); // 加once参数避免事件重复绑定 }) }) .catch(error => console.log('Error:', error)); // 这里拿到的就是用户选中的值,可直接用于后续fetch请求 result.then(userSelected => { console.log('用户选中的值:', userSelected); // 后续业务逻辑,比如携带userSelected发起新的fetch请求 });
关键改动点
- 去掉了多余的
nameArray嵌套,直接遍历接口返回的data数组生成选项,逻辑更简洁。 - 渲染完下拉框后返回封装的Promise,把
change事件的回调和Promise的resolve绑定,只有用户触发选择时Promise才会落定,后续的链式逻辑才会执行。 - 事件绑定加了
{ once: true }配置,用户选择后事件监听器会自动移除,避免内存泄漏。 - 删除了多余的
.then(resp => resp.json())逻辑,用户选择返回的是字符串值,不需要做json解析。
内容的提问来源于stack exchange,提问作者JackJack
相关产品推荐
相关产品推荐

