tl;dr:没有隐含的规则是回调不会阻塞。
Javascript 事件队列
在 Javascript 中,没有线程(WebWorkers 除外,但这是不同的)。这意味着如果您的代码的任何部分阻塞,整个应用程序都会被阻塞。回调没有什么神奇之处,它们只是函数:
function longLoop(cb) {
var i = 1000000;
while (i--) ;
cb();
}
function callback() {
console.log("Hello world");
}
function fun() {
longLoop(callback);
console.log("Called after Hello World is printed");
}
要进行异步操作,必须将回调放在事件队列中,这仅通过一些 API 调用发生:
- 用户发起的事件处理程序——点击、键盘、鼠标
- API 事件处理程序 - XmlHTTPRequest 回调、WebWorker 通信
- 计时函数 - setTimeout, setInterval
当事件被触发时,回调被放置在事件队列中,待所有其他回调完成执行后执行。这意味着不会有两行代码同时执行。
期货
我想你至少偶然发现了这篇关于期货的帖子。如果不是,那么它是一本好书,并且直接来自马口。
在 Javascript 中,您必须做一些工作才能理解异步代码。甚至还有一个名为futures的 Javascript 库,用于处理常见的事情,例如异步循环和序列(完全公开,我亲自与这个库的作者合作过)。
未来的概念是由创建未来的功能做出的承诺,即未来将在未来的某个时候完成。如果您要进行一些异步调用,请创建一个未来,将其返回,并在异步调用完成时履行承诺。来自博客文章:
Future<Results> costlyQuery() {
var completer = new Completer();
database.query("SELECT * FROM giant_table", (results) {
// when complete
completer.complete(results);
});
// this returns essentially immediately,
// before query is finished
return completer.future;
}
如果 database.query 是阻塞调用,future 将立即完成。这个例子假设database.query
是一个非阻塞调用(异步),所以future会在这个函数退出后实现。将使用指定的参数completer.complete
调用传递给的任何函数。completer.then()
您的示例,修改为惯用和异步:
void callback() {
print("callback called");
}
costlyQuery(sql) {
var completer = new Completer();
executeSql(sql, () => completer.complete());
return completer.future;
}
costlyQuery("select * from dual").then(callback);
这是异步的并正确使用期货。您可以将自己的回调函数传递给costlyQuery
您所做的事情,并在回调中调用它executeSql
以实现相同的目的,但这是 Javascript 的做法,而不是 Dart 的方式。
分离物
隔离类似于 Java 的线程,除了隔离是并发模型和线程是并行模型(有关并发与并行的更多信息,请参阅此 SO 问题)。
Dart 的特殊之处在于规范不要求所有内容都在单个线程中运行。它只要求不同的执行上下文不能访问相同的数据。每个隔离区都有自己的内存,并且只能通过发送/接收端口与其他隔离区通信(例如发送数据)。
如果您熟悉的话,这些端口的工作方式类似于 Go 中的通道。
隔离是独立于其他代码运行的隔离执行上下文。在 Javascript 术语中,这意味着它有自己的事件队列。请参阅Dart 导览,了解更完整的隔离物介绍。
假设您的 executeSql 是一个阻塞函数。如果我们不想等待它完成,我们可以将它加载到隔离中:
void callback() {
print("callback called");
}
void costlyQuery() {
port.receive((sql, reply) {
executeSql(sql);
reply.send();
});
}
main() {
var sendPort = spawnFunction(costlyQuery);
// .call does all the magic needed to communicate both directions
sendPort.call("select * from dual").then(callback);
print("Called before executeSql finishes");
}
此代码创建一个隔离,向其发送数据,然后在完成时注册一个回调。即使executeSql
阻塞,main()
也不一定会阻塞。