jjzjj

javascript - 使用生成器 + promise 在 Firefox SDK 附加组件中/与 Firefox SDK 附加组件进行 "simulated synchronous"通信

coder 2024-07-15 原文

TL;DR:有什么方法可以重写这个基于回调的 JavaScript 代码以改用 promises 和生成器吗?

背景

我有一个使用 Firefox Add-on SDK 编写的 Firefox 扩展.与 SDK 一样,代码分为附加脚本content script。 .这两个脚本具有不同类型的权限:附加脚本可以做一些奇特的事情,例如,通过 js-ctypes 调用 native 代码。界面,而内容脚本可以与网页交互。然而,附加脚本和内容脚本只能通过异步 message-passing interface彼此交互。 .

我希望能够从普通的非特权网页上的用户脚本调用扩展代码。这可以使用称为 exportFunction 的机制来完成这样一来,就可以将功能从扩展代码导出到用户代码。到目前为止,一切都很好。 然而,一个人只能使用exportFunction在内容脚本中,而不是附加脚本中。 这样就好了,只是我需要导出的函数需要使用前面提到的 js-ctypes 接口(interface),这只能在附加脚本中完成.

(编辑:事实证明不是您只能在内容脚本中使用 exportFunction 的情况。请参阅下面的评论。)

为了解决这个问题,我在内容脚本中编写了一个“包装器”函数;这个包装器是我实际通过 exportFunction 导出的函数.然后,我通过将消息传递给附加脚本,让包装函数在附加脚本中调用“真实”函数。这是内容脚本的样子;它正在导出函数 lengthInBytes :

// content script

function lengthInBytes(arg, callback) {
    self.port.emit("lengthInBytesCalled", arg);

    self.port.on("lengthInBytesReturned", function(result) {
        callback(result);
    });
}

exportFunction(lengthInBytes, unsafeWindow, {defineAs: "lengthInBytes",
                                             allowCallbacks: true});

这是附加脚本,其中 lengthInBytes 的“真实”版本被定义为。此处的代码监听内容脚本以向其发送 lengthInBytesCalled。消息,然后调用真实版本的lengthInBytes , 并在 lengthInBytesReturned 中发回结果信息。 (当然,在现实生活中,我可能不需要使用 js-ctypes 来获取字符串的长度;这只是一些更有趣的 C 库调用的替代品。发挥你的想象力。:) )

// add-on script

// Get "chrome privileges" to access the Components object.
var {Cu, Cc, Ci} = require("chrome");

Cu.import("resource://gre/modules/ctypes.jsm");
Cu.import("resource://gre/modules/Services.jsm");

var pageMod = require("sdk/page-mod");
var data = require("sdk/self").data;
pageMod.PageMod({
    include: ["*", "file://*"],
    attachTo: ["existing", "top"],
    contentScriptFile: data.url("content.js"),
    contentScriptWhen: "start", // Attach the content script before any page script loads.

    onAttach: function(worker) {
        worker.port.on("lengthInBytesCalled", function(arg) {
            let result = lengthInBytes(arg);
            worker.port.emit("lengthInBytesReturned", result);
        });
    }
});

function lengthInBytes(str) {
    // str is a JS string; convert it to a ctypes string.
    let cString = ctypes.char.array()(str);

    libc.init();
    let length = libc.strlen(cString); // defined elsewhere
    libc.shutdown();

    // `length` is a ctypes.UInt64; turn it into a JSON-serializable
    // string before returning it.
    return length.toString();
}

最后,用户脚本(只有安装了扩展才能工作)如下所示:

// user script, on an ordinary web page
lengthInBytes("hello", function(result) {
    console.log("Length in bytes: " + result);
});

我想做什么

现在,调用 lengthInBytes在用户脚本中是一个异步调用;它不是返回结果,而是在其回调参数中“返回”其结果。但是,在看到 this video 之后关于使用 promises 和生成器使异步代码更易于理解,我想知道如何以那种风格重写这段代码。

具体来说,我想要的是 lengthInBytes返回 Promise 以某种方式代表 lengthInBytesReturned 的最终有效负载信息。然后,在用户脚本中,我将有一个计算 yield lengthInBytes("hello") 的生成器得到结果。

但是,即使在观看了上面链接的视频并阅读了有关 promises 和生成器的内容之后,我仍然对如何将其连接起来感到困惑。 lengthInBytes 的一个版本返回 Promise看起来像:

function lengthInBytesPromise(arg) {
    self.port.emit("lengthInBytesCalled", arg);

    return new Promise(
        // do something with `lengthInBytesReturned` event???  idk.
    );
}

用户脚本会涉及类似的东西

var result = yield lengthInBytesPromise("hello");
console.log(result);

但这就是我所能弄清楚的。我将如何编写这段代码,调用它的用户脚本是什么样的?我想做的事有可能吗?

A complete working example of what I have so far is here.

感谢您的帮助!

最佳答案

下一个 下一个 JavaScript 版本 ECMAScript 7 将以 async functions 的形式为这个问题提供一个真正优雅的解决方案。 ,它们是 Promise 和 generators 的结合,弥补了两者的缺陷。在此答案的最底部有更多相关信息。

我是 Regenerator 的作者,一个在当今浏览器中支持 async 功能的转译器,但我意识到建议您在附加组件开发过程中引入编译步骤可能有点过分了,所以我将重点放在您提出的问题上实际上是在问:如何设计一个合理的 Promise 返回 API,以及使用此类 API 的最佳方式是什么?

首先,我将如何实现 lengthInBytesPromise:

function lengthInBytesPromise(arg) {
  self.port.emit("lengthInBytesCalled", arg);

  return new Promise(function(resolve, reject) {
    self.port.on("lengthInBytesReturned", function(result) {
      resolve(result);
    });
  });
}

function(resolve, reject) { ... } 回调在 promise 被实例化时被立即调用,resolvereject 参数是回调函数,可用于为 promise 提供最终值。

如果在这个例子中有失败的可能性,你可以传递一个Error对象给reject回调,但看起来这个操作是绝对可靠的,所以我们在这里可以忽略这种情况。

这就是 API 创建 promise 的方式,但消费者如何使用这样的 API?在您的内容脚本中,最简单的做法是调用 lengthInBytesPromise 并直接与生成的 Promise 交互:

lengthInBytesPromise("hello").then(function(length) {
  console.log(result);
});

在这种风格中,您将依赖于 lengthInBytesPromise 结果的代码放在传递给 promise 的 .then 方法的回调函数中,这看起来可能不像就像对回调 hell 的巨大改进,但至少如果您链接了一系列更长的异步操作,缩进更易于管理:

lengthInBytesPromise("hello").then(function(length) {
  console.log(result);
  return someOtherPromise(length);
}).then(function(resultOfThatOtherPromise) {
  return yetAnotherPromise(resultOfThatOtherPromise + 1);
}).then(function(finalResult) {
  console.log(finalResult);
});

生成器可以帮助减少此处的样板文件,但需要额外的运行时支持。可能最简单的方法是使用 Dave Herman 的 task.js library :

spawn(function*() { // Note the *; this is a generator function!
  var length = yield lengthInBytesPromise("hello");
  var resultOfThatOtherPromise = yield someOtherPromise(length);
  var finalResult = yield yetAnotherPromise(resultOfThatOtherPromise + 1);
  console.log(finalResult);
});

这段代码更短,回调更少,这是肯定的。您可以猜到,大部分魔法只是简单地转移到了 spawn 函数中,但它的实现实际上非常简单。

spawn 函数接受一个生成器函数并立即调用它得到一个生成器对象,然后调用生成器对象的 gen.next() 方法得到首先 yielded promise(lengthInBytesPromise("hello") 的结果),然后等待该 promise 实现,然后调用 gen.next(result) 结果,它为第一个 yield 表达式(分配给 length 的表达式)提供一个值,并导致生成器函数运行到下一个 yield 表达式(即 yield someOtherPromise(length)),生成下一个 promise,依此类推,直到没有更多的 promise 等待等待,因为生成器函数最终返回.

为了让您体验 ES7 中的新功能,下面是您可以如何使用 async 函数来实现完全相同的功能:

async function process(arg) {
  var length = await lengthInBytesPromise(arg);
  var resultOfThatOtherPromise = await someOtherPromise(length);
  var finalResult = await yetAnotherPromise(resultOfThatOtherPromise + 1);
  return finalResult;
}

// An async function always returns a Promise for its own return value.
process(arg).then(function(finalResult) {
  console.log(finalResult);
});

这里真正发生的是 async 关键字取代了 spawn 函数(和 * 生成器语法),并且 await 已经取代了 yield。这不是一个巨大的飞跃,但是将这种语法内置到语言中而不是必须依赖像 task.js 这样的外部库将会非常好。

如果您对使用 async 函数而不是 task.js 感到兴奋,那么一定要查看 Regenerator !

关于javascript - 使用生成器 + promise 在 Firefox SDK 附加组件中/与 Firefox SDK 附加组件进行 "simulated synchronous"通信,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27262373/

有关javascript - 使用生成器 + promise 在 Firefox SDK 附加组件中/与 Firefox SDK 附加组件进行 "simulated synchronous"通信的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  8. ruby-on-rails - rails : "missing partial" when calling 'render' in RSpec test - 2

    我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou

  9. ruby-on-rails - 由于 "wkhtmltopdf",PDFKIT 显然无法正常工作 - 2

    我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-

  10. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

随机推荐