你的位置:首页 > Java教程

[Java教程][Effective JavaScript 笔记]第7章:并发


前言

这一章的内容学到了事件队列和异步的API。js只是运行在其他应用程序的脚本语言。js即依赖于应用程序,也独立与应用程序。可以使它可以在多平台,多种环境上运行。ECMAScript标准中没有关于并发的说明。这章讨论的是一些常用的方法,使用事件和异步API是js编程的基础部分。异步API,有setTimeout,setInterval。

第61条:不要阻塞I/O事件队列

个人总结

js是构建在事件之上的单线程语言。js处理交互都以事件的方法进行传递的,监听事件的处理函数,都根据事件队列的执行相应的监听函数。

同步处理

同步处理,在一个I/O请求中,会等待输入的内容。如果没有输入会一直等待下去,直到输入有结果。这个时候,在多线程的语言里可以开另一个线程处理其他计算,但js是单线程的语言,只能一直等,也就是阻塞了,浪费了计算机资源。

异步处理

在js中,可以为一个I/O操作提供一个回调函数,然后程序会继续处理下面的代码。直到I/O有输入时,回调函数才会执行相关操作。这是由事件队列的特性来实现的,这样就可以实现无阻塞的代码。

提示

  • 异步API使用回调函数来延缓处理代价高昂的操作以避免阻塞主应用程序

  • js并发地接收事件,但会使用一个事件队列按序地处理事件处理程序

  • 在应用程序事件队列中绝不要使用阻塞的I/O

第62条:在异步序列中使用嵌套或命名的回调函数

个人总结

理解操作序列的最简单的方式是异步API的发起操作而不是执行操作。所以在发起操作后的代码会先执行,而到后面的事件循环的轮次中,被注册的事件处理程序才会执行。串联已完成的异步操作,可以使用嵌套的方式来进行。但层次过多会导致代码很乱,很长。减少嵌套的方法:使用命名函数,使用bind方法来绑定。把这些方式结合在一起使用,可以更好解决问题。

提示

  • 使用嵌套或命名的回调函数按顺序地执行多个异步操作

  • 尝试在过多的嵌套的回调函数和尴尬的命名的非嵌套回调函数之间取得平衡

  • 避免将可被并行执行的操作顺序化

第63条:当心丢弃错误

个人总结

管理异步编程,调试不太容易,错误发生的地方和错误捕获的地方不好定位。这里对异步操作把错误的信息的以回调函数参数的形式向外层传递。在回调函数中对错误进行处理,可以使代码可以正常运行。

提示

  • 通过编写共享的错误处理函数来避免复制和粘贴错误处理代码

  • 确保明确地处理所有的错误条件以避免丢弃错误

第64条:对异步循环使用递归

个人总结

异步循环,62条所说的一样,这里的循环只是同时发起了多个操作,但并不是执行操作。所以无法在执行操作中对循环进行中止。把循环的操作改写成函数的递归,把异步的发起和执行进行序列化。但又会有一个新的问题,js环境通常在内存中保存一块固定的区域,称为调用栈,用于记录函数调用返回前下一步该做什么。这是以栈的方式来存储的“先进后出”。但如果这样的调用次数过多,会导致栈空间被耗尽,最终会抛出异常,即栈溢出。

提示

  • 循环不能是异步的

  • 使用递归函数在事件循环的单独轮次中执行迭代

  • 在事件循环的单独轮次中执行递归,并不会导致调用栈溢出

第65条:不要在计算时阻塞事件队列

个人总结

第61条解释了异步API如何防止一段程序阻塞应用程序的事件队列。如果是一段正常的执行代码一直占用线程,事件队列中的操作无法执行。这段时间里在浏览器环境中,无法响应
用户操作。Web客户端平台的Worker API,可以处理纯数据的计算,从而防止计算时对事件队列的阻塞。

提示

  • 避免在主事件队列中执行代价高昂的算法

  • 在支持Worker API的平台,该API可以用来在一个独立的事件队列中运行长计算程序

  • 在Worker API不可用或代价昂贵的环境中,考虑将计算程序分解到事件循环的多个轮次中

第66条:使用计数器来执行并行操作

个人总结

当处理多个并发时,无法保重回调函数中参数的顺序。导致后期代码无法正确运行,对于每次发起操作记一次数,返回时对对应的返回结果进行记录。回调函数再对记录的结果进行处理。可以保证程序按预定步骤进行运行。

提示

  • js应用程序中的事件发生是不确定的,即顺序是不可预测的

  • 使用计数器避免并行操作中的数据竞争

第67条:绝不要同步地调用异步的回调函数

个人总结

异步的返回结果,可以保存在一个缓存中。在这种情况下,再进行多文件同步下载,可以使用缓存中的数据,所以回调函数可以同步地执行。但就像64条上的调用栈有可能会出现问题,会导致栈空间耗尽。回调函数也使用异步调用,使用setTimeout来调用对应的回调函数。

提示

  • 即使可以立即得到数据,也绝不要同步地调用异步回调函数

  • 同步地调用异步的回调函数扰乱了预期的操作序列,并可能导致意想不到的交错代码

  • 同步地调用异步的回调函数可能导致栈溢出或错误地处理异常

  • 使用异步的API,比如setTimeout函数来调度异步回调函数,使其运行于另一回合

第68条:使用promise模式清洁异步逻辑

个人总结

使用promise模式,可以把多层嵌套函数,改写成一种同步传入回调函数的方式。这样可以利用各种工具函数对其进行处理。如then,when,join,select等。

提示

  • promise代表最终值,即并行操作完成时最终产生的结果

  • 使用promise组合不同的并行操作

  • 使用promise模式的API避免数据竞争

  • 在要求有意的竞争条件时使用select(也被称为choose)

总结

  • 异步调用,利用事件队列防止阻塞

  • 函数的多次递归调用,调用栈可能会耗尽

  • 纯计算,使用web客户端的Worker API

  • 使用计数器,可以保证异步的结果顺序

  • 使用处理异步调用的工具框架

  • 可以防止数据竞争