如何在同步函数返回的对象中解析Promise为实际值?
我需要在一个受API约束无法声明为async的同步函数中返回一个数据对象,但部分数据为Promise类型。我可以通过Promise.all([<promises>]).then(callback)延迟回调至Promise解析完成,但返回的对象仍包含大量Promise。如何无需逐个处理所有项就将对象中所有Promise替换为其解析后的值?
背景
本次场景为开发Chrome扩展。Chrome扩展分为两部分:作为核心脚本的后台「service worker」,以及作用于每个页面的「content scripts」。部分操作只能在后台脚本中完成,比如获取当前窗口的标签页,这需要通过消息API从content scripts调用后台函数。
细节
当前API实现如下:
chrome.runtime.onMessage.addListener(messageHandler) // Example "message" object: { get: { globalFunctions: ['getCurrentTab'] } } function messageHandler(message, sender, callback) { const responseObject = {} const promises = [] if (message.get) { const get = message.get const res = {} if (get.globalFunctions) { const funcNames = conformToStringArr(get.globalFunctions) if (!funcNames) return; res.globalFunctions = {} funcNames.forEach((funcName) => { res.globalFunctions[funcName] = globalFunctions[funcName](message, sender) // Functions can be async because they have to asynchronously retrieve values }) } if (get.globalValues) { const globalNames = conformToStringArr(get.globalValues) if (!globalNames) return; res.globalValues = {} globalNames.forEach((valueName) => { res.globalValues[valueName] = globalValues[valueName] }) } if (get.localStorage) { const toGet = conformToStringArr(get.localStorage) if (!toGet) return; res.localStorage = chrome.storage.local.get(toGet) // : Promise } responseObject.get = res } callback(responseObject) return true; // Required due to restrictions of the message API. I cannot return a Promise or use an asynchronous function. }
本质上我是按同步逻辑填充对象并返回,但问题在于部分值是Promise,而Promise引用不会自动解析为对应值。我已掌握Promise的控制流逻辑,但这种值替换的方法在网上鲜有提及。
我可以将所有Promise加入数组,通过Promise.all(promises).then(callback)在所有Promise完成后再返回对象,但这样返回的对象仍会充满Promise,对吗?
如何从这类非async函数中返回不含Promise的对象?
解决方案
改为将赋值操作包裹在Promise中。例如,不要使用outObject.bar = asyncFoo()(asyncFoo返回Promise),而是使用asyncFoo().then((value) => outObject.bar = value)。为确保所有值填充完成后再返回对象,将每个Promise加入「promises」数组,并在函数末尾添加Promise.allSettled(promises).then(() => callback(outObject))。
最终代码:
function messageHandler(message, sender, callback) { const responseObject = {} const promises = [] if (message.get) { const get = message.get responseObject.get = {} if (get.globalFunctions) { const funcNames = conformToStringArr(get.globalFunctions) if (funcNames) { responseObject.get.globalFunctions = {} funcNames.forEach((name) => { const functionResult = globalFunctions[name](message, sender) if (functionResult instanceof Promise) promises.push(functionResult.then((value) => responseObject.get.globalFunctions[name] = value)) else responseObject.get.globalFunctions[name] = functionResult }) } } if (get.globalValues) { const globalNames = conformToStringArr(get.globalValues) if (globalNames) { responseObject.get.globalValues = {} globalNames.forEach((valueName) => { responseObject.get.globalValues[valueName] = globalValues[valueName] }) } } if (get.localStorage) { const toGet = conformToStringArr(get.localStorage) if (toGet) promises.push(chrome.storage.local.get(toGet).then((value) => responseObject.get.localStorage = value)) } } if (promises.length > 0) { Promise.allSettled(promises).then(() => callback(responseObject)) } else { callback(responseObject) } return true; }
内容的提问来源于stack exchange,提问作者ByThePowerOfScience

