如果您的目标浏览器支持 JavaScript 1.7 或更高版本(在撰写本文时,我认为只有 Gecko 浏览器支持),那么您可以利用生成器来执行此操作。让我们首先使您的connect
函数异步(并且更易于测试):
function connectAsync(callback) {
// Pretend we always succeed after a second.
// You could use some other asynchronous code instead.
setTimeout(callback, 1000, true);
}
现在将您的调用代码包装到一个函数中。(它必须在一个函数中。)
function main() {
console.log("Connecting...");
if(!(yield connect())) {
console.log("Failed to connect.");
yield; return;
}
console.log("Connected.");
yield;
}
请注意所有yield
地方的所有 s。这是魔法的一部分。我们yield
在调用时需要connect
一个,yield
在每个函数退出点之前都需要一个。
现在我们需要定义connect
. 您可能有一堆想要同步的异步函数,所以让我们创建一个使函数同步的函数。
function synchronize(async) {
return function synchronous() {
return {func: async, args: arguments};
};
}
然后我们可以connect
用connectAsync
它创建:
var connect = synchronize(connectAsync);
现在我们需要创建一个运行所有这些魔法的函数。这里是:
function run(sync) {
var args = Array.prototype.slice.call(arguments);
var runCallback = args.length ? args[args.length - 1] : null;
if(args.length) {
args.splice(args.length - 1, 1);
}
var generator = sync.apply(window, args);
runInternal(generator.next());
function runInternal(value) {
if(typeof value === 'undefined') {
if(runCallback) {
return runCallback();
}else{
return;
}
}
function callback(result) {
return runInternal(generator.send(result));
}
var args = Array.prototype.slice.call(value.args);
args.push(callback);
value.func.apply(window, args);
}
}
现在你可以main
用这个魔法运行你的函数:
run(main);
我应该注意,它不再可能main
返回一个值。run
可以接受第二个参数:同步函数完成时要调用的回调。要将参数传递给main
(例如1
、2
和3
),您可以这样称呼它:
run(main, 1, 2, 3, callback);
如果您不想要回调,请传递null
或undefined
。
要尝试这一点,您必须将type
标签的script
设置为text/javascript; version=1.7
。JavaScript 1.7 添加了新的关键字(如yield
),因此它是向后不兼容的,因此,必须有某种方式将自己与旧代码区分开来。
有关生成器的更多信息,请参阅MDN 上的此页面。有关协程的更多信息,请参阅Wikipedia 上的文章。
尽管如此,由于对 JavaScript 1.7 的支持有限,这并不实用。此外,这种代码并不常见,因此可能难以理解和维护。我建议只使用异步风格,如Deisss 的回答所示。但是,可以做到这一点很巧妙。