Open lmk123 opened 10 months ago
在开发 Chrome 扩展程序的时候,大部分 chrome.* 接口都是类似于下面这样的 callback 风格:
chrome.tabs.create({ url: 'https://hcfy.app' }, tab => { console.log(tab.id) })
新出的 Manifest V3 同时支持 callback 风格与 Promise 风格:
// Manifest V3 中的 Promise 风格 const tab = await chrome.tabs.create({ url: 'https://hcfy.app' }) console.log(tab.id)
但划词翻译目前仍然停留在 Manifest V2,能且只能用 callback 写法,但我又想要用 Promise,怎么办?
几年前,我曾开发过一个项目叫 chrome-call,它通过如下方式使用:
import chromeCall from 'chrome-call' const tab = await chromeCall(chrome.tabs, 'create', { url: 'https://hcfy.app' })
现在,我更喜欢 Node.js 中 util.promisify() 的形式,而 chrome 版本的 promisify() 函数很快就写好了:
promisify()
function chromePromisify(fn) { return function (...args) { return new Promise((resolve, reject) => { fn.call(null, ...args, (result) => { const error = chrome.runtime.lastError if (error) { reject(error) } else { resolve(result) } }) }) } } const tabsCreate = chromePromisify(chrome.tabs.create) const tab = await tabsCreate({ url: 'https://hcfy.app' })
但是接下来,在给这个函数添加 TypeScript 类型注解的时候,我犯了难。
function chromePromisify(fn: (...args: any[]) => void) { return function (...args: any[]) { return new Promise((resolve, reject) => { fn.call(null, ...args, (result: unknown) => { const error = chrome.runtime.lastError if (error) { reject(error) } else { resolve(result) } }) }) } }
这样确实能用,但会丢掉 @types/chrome 里对 chrome.tabs.create 函数的类型提示:
chrome.tabs.create
// 直接使用时,TypeScript 能检测参数的类型 chrome.tabs.create({ url: 'https://hcfy.app' }, tab => { // 并且能推断出 tab 的类型是 chrome.Tabs.tab console.log(tab.id) }) // 用了 chromePromisify 之后 const tabsCreate = chromePromisify(chrome.tabs.create) // 参数是 any[] 所以填什么都不会报类型错,返回值类型是 unknow 所以必须自行声明类型 const tab: chrome.Tabs.tab = await tabsCreate({ url: 'https://hcfy.app' })
我希望这个函数能更加智能,所以参考了 @types/node 中对 util.promisify() 的类型注解,写出了第二个版本:
function chromePromisify<TResult>(fn: (callback: (result: TResult) => void) => void): () => Promise<TResult> function chromePromisify<T1, TResult>(fn: (arg1: T1, callback: (result: TResult) => void) => void): (arg1: T1) => Promise<TResult> // 后面就是不断增加 T1、T2、T3…… function chromePromisify(fn: Function): Function { return function (...args: any[]) { return new Promise((resolve, reject) => { fn.call(null, ...args, (result: unknown) => { const error = chrome.runtime.lastError if (error) { reject(error) } else { resolve(result) } }) }) } }
在用 chrome.tabs.create 方法做测试时,这个版本很好的达成了我的预期,点击此链接可以看到 TypeScript 成功在参数类型错误时报了错,且能正确推断出返回值的类型。
但是在实际使用中,我发现它对于有多个重载的函数不起作用,比如 chrome.windows.get,点击此链接可以看到具体情况。
chrome.windows.get
我想更进一步,让它支持有多个重载的函数。我猜测应该可以用 infer 关键字提取出函数的参数类型,于是搜了一下,然后就被密密麻麻的代码劝退了:https://stackoverflow.com/a/74209026
infer
不过我还是想优化一下第二个版本那种不断重复添加 T1、T2、T3 的写法。我想要的类型是最后一个参数是 callback 的函数,而我不想通过重复给它添加 T1、T2、T3 的重载来获取到正确的类型。
在经过一番搜索之后,我在这里查到了下面这种写法:
type Bar = any type Qux = number // 前面几个都是 Bar 类型,但最后一个是 Qux 的 tuple type WithLastQux = [...Bar[], Qux]
这种形式不正好能满足我想要的“最后一个参数是 callback 的函数”的类型吗:
type FnWithLastCallback = (...args: [...unknow[], (result: unknow) => void]) => void
上面这个类型可以再进一步,提取出参数列表与函数结果:
type FnWithLastCallback<TArgs extends Array<unknow>, TResult> = (...args: [...TArgs, (result: TResult) => void]) => void
然后,我们用这个类型来改造上面的 chromePromisify:
chromePromisify
function chromePromisify<TArgs extends Array<unknown>,TResult>(fn: (...args:[...TArgs, (result:TResult)=>void])=>void) { return function (...args: TArgs) { return new Promise<TResult>((resolve, reject) => { fn.call(null, ...args, (result: TResult) => { const error = chrome.runtime.lastError if (error) { reject(error) } else { resolve(result) } }) }) } }
这样,就不用重复声明 T1、T2、T3 类型了。点击这里查看
hello,有个问题想请教您,方便联系您一下吗?
背景
在开发 Chrome 扩展程序的时候,大部分 chrome.* 接口都是类似于下面这样的 callback 风格:
新出的 Manifest V3 同时支持 callback 风格与 Promise 风格:
但划词翻译目前仍然停留在 Manifest V2,能且只能用 callback 写法,但我又想要用 Promise,怎么办?
解决方案
几年前,我曾开发过一个项目叫 chrome-call,它通过如下方式使用:
现在,我更喜欢 Node.js 中 util.promisify() 的形式,而 chrome 版本的
promisify()
函数很快就写好了:但是接下来,在给这个函数添加 TypeScript 类型注解的时候,我犯了难。
第一个版本的类型注解
这样确实能用,但会丢掉 @types/chrome 里对
chrome.tabs.create
函数的类型提示:第二个版本的类型注解
我希望这个函数能更加智能,所以参考了 @types/node 中对 util.promisify() 的类型注解,写出了第二个版本:
在用
chrome.tabs.create
方法做测试时,这个版本很好的达成了我的预期,点击此链接可以看到 TypeScript 成功在参数类型错误时报了错,且能正确推断出返回值的类型。但是在实际使用中,我发现它对于有多个重载的函数不起作用,比如
chrome.windows.get
,点击此链接可以看到具体情况。第三个版本
我想更进一步,让它支持有多个重载的函数。我猜测应该可以用
infer
关键字提取出函数的参数类型,于是搜了一下,然后就被密密麻麻的代码劝退了:https://stackoverflow.com/a/74209026不过我还是想优化一下第二个版本那种不断重复添加 T1、T2、T3 的写法。我想要的类型是最后一个参数是 callback 的函数,而我不想通过重复给它添加 T1、T2、T3 的重载来获取到正确的类型。
在经过一番搜索之后,我在这里查到了下面这种写法:
这种形式不正好能满足我想要的“最后一个参数是 callback 的函数”的类型吗:
上面这个类型可以再进一步,提取出参数列表与函数结果:
然后,我们用这个类型来改造上面的
chromePromisify
:这样,就不用重复声明 T1、T2、T3 类型了。点击这里查看