术语“异步”和“并行”常常被混为一谈,异步是关于现在和将来的时间间隙,而并行是关于能够同时发生的事情。
并行计算最常见的工具就是进程和线程。进程和线程独立运行,并可能同时运行:在不同的处理器,甚至不同的计算机上,但多个线程能够共享单个进程的内存。与之相对的是,事件循环把自身的工作分成一个个任务并顺序执行,不允许对共享内存的 并行访问和修改。通过分立线程中彼此合作的事件循环,并行和顺序执行可以共存。
并行线程的交替执行和异步事件的交替调度,其粒度是完全不同的。
多线程编程是非常复杂的。因为如果不通过特殊的步骤来防止这种中断和交错运行的话,可能会得到出乎意料的、不确定的行为,通常这很让人头疼。
js 从不跨线程共享数据,这意味着不需要考虑这一层次的不确定性。但是这并不意味着 js 总是确定性的。

  1. var a = 20;
  2. function foo() {
  3. a = a + 1;
  4. }
  5. function bar() {
  6. a = a * 2;
  7. }
  8. // ajax(..)是某个库中提供的某个Ajax函数
  9. ajax( "http://some.url.1", foo );
  10. ajax( "http://some.url.2", bar );

foo()bar() 的相对顺序改变可能会导致不同结果

完整运行

由于 js 的单线程特性,foo()(以及 bar())中的代码具有原子性。也就是说,一旦 foo() 开始运行,它的所有代码都会在 bar() 中的任意代码运行之前完成,或者相反。 这称为完整运行(run-to-completion)特性。
实际上,如果 foo()bar() 中的代码更长,完整运行的语义就会更加清晰,比如:

  1. var a = 1;
  2. var b = 2;
  3. function foo() {
  4. a++;
  5. b = b * a;
  6. a = b + 3; }
  7. function bar() {
  8. b--;
  9. a = 8 + b;
  10. b = a * 2;
  11. }
  12. // ajax(..)是某个库中提供的某个Ajax函数
  13. ajax( "http://some.url.1", foo );
  14. ajax( "http://some.url.2", bar );

由于 foo() 不会被 bar() 中断,bar() 也不会被 foo() 中断,所以这个程序只有两个可能的输出,取决于这两个函数哪个先运行——如果存在多线程,且 foo()bar() 中的语句可 以交替运行的话,可能输出的数目将会增加不少!
在 js 的特性中,这种函数顺序的不确定性就是通常所说的竞态条件(race condition),foo()bar() 相互竞争,看谁先运行。具体来说,因为无法可靠预测 ab 的最终结果,所以才是竞态条件。