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
当事件被触发时,回调被放置在事件队列中,待所有其他回调执行完毕后执行。这意味着不会有两行您的代码会同时执行。
期货
我假设您至少偶然发现了this post about futures。如果没有,那是一本好书,而且直接来自马口。
在 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是一个非阻塞调用(async),所以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 方式。
分离物
Isolates 类似于 Java 的线程,只是isolates 是一种并发模型,而线程是一种并行模型(有关并发与并行的更多信息,请参阅this SO question)。
Dart 的特殊之处在于规范并未强制要求所有内容都在单个线程中运行。它只要求不同的执行上下文不能访问相同的数据。每个隔离区都有自己的内存,并且只能通过发送/接收端口与其他隔离区通信(例如发送数据)。
如果您熟悉的话,这些端口的工作方式类似于 Go 中的通道。
isolate 是独立于其他代码运行的隔离执行上下文。在 Javascript 术语中,这意味着它有自己的事件队列。请参阅 the Dart tour 了解更完整的分离株介绍。
假设您的 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() 也不一定会阻塞。