在以上两种runCalculation实现中,有时会用到返值技术。这是出于简洁的目的而随意作出的选择。下面这行代码 return callback(calculationCache[formula]); 很容易即可改写成 callback(calculationCache[formula]); return; 这是因为并没有打算使用这个返值。这是JavaScript的一种普遍做法,而且通常无害。 不过,有些函数既返回有用的值,又要取用回调。这类情况下,切记回调有可能被同步调用(返值之前),也有可能被异步调用(返值之后)。 永远不要定义一个潜在同步而返值却有可能用于回调的函数。举个例子,下面这个负责打开WebSocket 连接以连至给定服务器的函数(使用缓存技术以确保每个服务器只有一个连接)就违反了上述规则。 var webSocketCache = {}; function openWebSocket(serverAddress, callback) { var socket; if (serverAddress in webSocketCache) { socket = webSocketCache[serverAddress]; if (socket.readyState === WebSocket.OPEN) { callback(); } else { socket.onopen = _.compose(callback, socket.onopen); }; } else { socket = new WebSocket(serverAddress); webSocketCache[serverAddress] = socket; socket.onopen = callback; }; return socket; }; (这段代码依赖于Underscore.js库。_.compose定义的这个新函数既运行了callback,又运行了初始的socket.onopen回调。 ) 这段代码的问题在于,如果套接字已经缓存且打开,则会在函数返值之前就运行回调,这会使以下代码崩溃。 var socket = openWebSocket(url, function() { socket.send('Hello, server!'); }); 怎么解决呢?将回调封装在setTimeout中即可。 if (socket.readyState === WebSocket.OPEN) { setTimeout(callback, 0); } else { // ... } 这里使用延时会让人感觉是在东拼西凑,但这总比API自相矛盾要好得多。 在本节中,我们看到了一些编写异步函数的最佳实践。请勿依赖那些看似始终异步的函数,除非已经阅读其源代码。请避免使用计时器方法来等待某个会变化的东西。如果同一个函数既返值又运行回调,则请确保回调在返值之后才运行。 一次消化这些信息确实太多了一点,不过,编写好的异步函数确实是写出优秀JavaScript代码的关键所在。