Javascript引擎从设计伊始就是单线程的,然而真实世界的事件往往是多头并进的。为了解决这个问题,浏览器通过UI事件、网络事件及回调函数引入了一定程度的并发执行可能(但Javascript引擎仍然是单线程的,这种并发,类似于其它语言的协程)。

在复杂的程序里,回调函数是一个坑,它使得代码变得支离破碎,难以阅读。后来出现了Promise,但也没能完全解决上述问题。

Javascript的最新方法是async/await,一种在其它语言中早已实现的方案。

一个典型的回调场景

在其它语言里,代码经常是顺序执行的:当代码执行到第二行时,第一行的代码确定已经执行,并且第二行可以利用其结果。即使这里遇到多线程或者其它异步的情况,这些程序也提供了等待机制,以确保代码仍然是顺序执行的。

但在Javascript中,由于之前没有这种等待机制,如果遇到异步的情况,则只能使用回调机制来确保逻辑按序执行。比如,如果我们的代码必须在文档加载完成之后执行,我们就必须利用浏览器提供的回调机制:

window.onload = function main(){}

现在我们来看在一个复杂的工程中,这种回调机制会有多困难。当然,为了讨论方便,我们只会截取这类工程中最简单的部分来看:

假设我们程序的入口为main函数,由于这是一个商业应用,多语言支持和多浏览器支持是必须的,我们可能要做以下事情: 1. 根据浏览器类型和版本,决定要打哪些补丁。 2. 在补丁完成后,根据用户选择的语言,加载对应的语言包。 3. 现在才能开始我们的程序逻辑部分。

这个函数的伪代码如下:

function main(){

//section 1 if (browser === 'ie'){

ajax_load('/scripts/ie_patch.js')

// section 2 if (lang === 'chinese'){

ajax_load('/lang/zh_CN.js')

// section 3, the application business}

假设section 1、section 2和section 3是逐级依赖的,即要执行section 3,必须等section 2的代码执行完毕;要执行section 2,则又必须等待section 1执行完成,否则,程序会出错。从伪代码来看,相当简单,对吧?

这里我们使用了一个名为ajax_load的函数,你可以把它当成XMLHttpRequest,或者jQuery的ajax。

问题是,目前没有一个ajax_load可以同步执行(我们先不考虑性能要求),所以可实现的方案(利用回调)必然是:

function main(){

if (browser === 'ie'){

ajax_load('/scripts/ie_patch.js', on_success = function(response){

if (lang === 'chinese'){

ajax_load('/lang/zh_CN.js', on_success = function(response){

//section 3, the application business }))

}else{

// use default en language //section 3, the application business }

}else{

if (lang === 'chinese'){

ajax_load('/lang/zh_CN.js', on_success = function(response){

// section 3, the application business })

}else{

// use default en language //section 3, the application business }

上面的代码已经省去了复杂的错误处理。即便这样,这段代码很好地揭示了在存在多个条件判断,又只能通过回调来实现异步调用时,即使只是写上一小段代码也是多么困难,重复和冗余的代码又是如何之多。

Promise的问题

Promise甫一引入时,Javascript程序员就对其寄予了较大的期望。但实际上,Promise对上述问题的改善并不显著。我们使用Promise来改写上述main代码。

这里我们不再使用ajax_load这一伪代码,而是使用现代浏览器都已实现的一个新的API -- fetch。它将返回一个Promise对象。

function main(){

if (browser === 'ie'){

fetch('/script/ie_patch.js').then(response => response.text()).then(script => {

eval(script)

if (lang === 'chinese'){

fetch('/lang/zh_CN.js').then(response => response.json()).then(script =>{

// use script //section 3, the application business })

//section 3, the application business })

if (lang === 'chinese'){

fetch('/lang/zh_CN.js').then(response => response.json()).then(script =>{

// use script //section 3, the application business })

当然上面的代码可以做一些优化,即将除fetch之外的代码都写成Promise,这样就可以一路链式调用下去,使用程序看上去是顺序执行的。但整个程序仍然是很复杂的。

为什么引入Promise之后,还会这样呢?本质上,Promise就是一种改写的回调,只不过,这个回调通过then来调用而已。也就是把之前写在异步函数调用体内部的回调逻辑,通过Promise.then改写到了外面了。

我们再来看一小段代码:

a = 0

p = new Promise(function(resolve, reject){

counter = 0

timer = setInterval(function(){

console.info(counter++)

}, 1000)

setTimeout(function(){

a = 5

resolve(a)

clearInterval(timer)

}, 5000)

p.then(a=>console.info(`Promise yield${a}`)) //1console.info(`a is${a}outside of Promise`) //2

输出如下:

a is 0 outside of Promise

undefined

Promise yield 5

我们通过setTimeout来模拟了一个异步函数,并将它封装在一个Promise当中。这个异步函数在启动时触发一个计时器,它将在控制台打印出计数器,每秒输出一个数字。resolve, reject是系统(浏览器)传给我们异步函数的两个信号触发器函数,当你的异步函数已经执行完成,得到结果时,就调用resovle,并且将结果传给这个resolve(再经resolve传递给你,见代码行1)。如果出错,则调用reject来触发错误处理机制。

我们从输出中可以看到,代码并没有顺序执行:当代码执行到行1时,并没有等待结果发生,而是立即去执行行2,结果输出"a is 0 outside of Promise";然后异步函数开始输出计数器,并在第5秒时,异步函数结束执行,将结果返回给p.then,这样我们就看到了最后一行输出:

Promise yield 5

从上面的实验可以看出,除非所有的代码都书写成Promise,否则,Promise仍然不能改变异步代码的同步执行问题。而且,就算你这样做了,长达数个或者数十个函数的调用链也是看上去很奇怪的一件事。

Async/Await

ES7引入了关键字Async/Await关键字,从根本上解决了这一问题。我们看看MDN对它的介绍:

这正是我们想要的。一方面,我们需要异步(并发)来提高程序性能,另一方面,从程序的逻辑层面来看,事情仍然是遵循因果律的,代码的结构必须看上去是同步的,至于如何实现,应该交给底层去考虑。

定义async函数

async foo(){

console.info("this is an asynchronous function")

return 1

foo()

---output---

Promise {: 1}

当我们使用async来修饰一个普通函数时,Javascript引擎将自动将其封装成一个Promise对象,并且其状态是resolved,并且普通函数的返回值就是resolve值。

当然我们也可以在函数中返回一个Promise对象:

async function foo(){

return new Promise(function(resolve, reject){

setTimeout(function(){

resolve('hello world!')

}, 3000)

foo()

---output---

Promise {}

这时候Javascript引擎将不再进一步封装。这种情况下,函数是否用async关键字修饰是无关紧要的,但为代码便于阅读和理解起见,建议仍然加上这一关键字。

有两种调用方式,一是在async函数中调用另一个async函数,我们一般使用await关键字,这样可以实现代码的同步调用:

async bar(){

let output = await foo()

console.info(`foo() returned${output}3 seconds later`)

第一个async函数怎么调用呢,答案是通过Promise.then()来调用,因为async函数的返回值一定是一个Promise对象。

bar().then(()=>console.info("started bar"))

现在我们再来改写最开始的程序,这次代码将清晰很多:

async function main(){

if (browser === 'ie'){

let response = await fetch('/script/ie_patch.js')

let script = await response.text()

eval(script)

if (lang === 'chinese'){

let response = await fetch('/lang/zh_CN.js')

let lang = await response.text()

apply_lang(lang)

// section 3, start out business here}

// start mainwindow.onload = function(){

main.then(()=>console.info("the application started!")

更高级的async用法

等待多个异步调用结果

上面的main例子很好地演示了如何简单地使用系统提供的异步函数,的确很简单易用。 如果我们要等待多个异步调用的结果,直到它们完成再执行下一段,我们还要用到Promise.all:

let results = await Promise.all([

fetch(url1),

fetch(url2),

在Promise语境下,错误处理是通过Promise.catch()来完成的,catch和then混在一起,代码的可读性很差。在async/await语境上,我们象处理普通的异常一样来进行错误处理:

async function bar(){

return new Promise(function(resolve, reject){

setTimeout(function(){

reject("bar failed due to timeout")

}, 5000)

async function foo(){

try {

await bar()

}catch(e){

console.error("we're rejected by bar")

看起来async/await很多地方借用了Promise。当你的代码调用reject时,就会抛出一个error,从而被外面的catch捕捉到。

使用外部的resolve, reject

我们前面定义的几个异步函数的例子(这也是大多数文章所引用的),在实际应用中作用几乎等于零。这是因为,在这些例子当中,异步函数的实际返回值都是当场决定的:

async foo(){

return 1

async bar(){

return new Promise(function(resolve, reject){

setTimeout(function(){

resolve("hello world")

函数foo只是纯粹演示语法。如果我们在这一刻就知道函数的结果,又有什么必要使用异步呢?函数bar返回了一个Pending态的promise,但这个promise的resolve仍然要发生成Promise构造器内部,它又能决定什么,以及凭何决定呢(要做出一个resolve所需要的状态很可能在将来才会出现,但在Promise构造时,又只能使用当前可见的变量及状态,并将其生成闭包)。

事实是,对能接触底层的程序员来说,他们能在自己的代码内部实现异步,并且返回Promise对象供上层程序员(应用程序员)通过async/await来调用;而对应用程序员,有可能在复杂的程序中,需要等待多个异步执行的结果,或者对某个异步执行的结果进行运算,并将这种运算封装起来。要完成这样的任务,就必须使用外部的resolve和reject。

回想一下究竟什么是resolve和reject。本质上它们是两个发信号的函数指针,当应用程序调用其中之一时,外面等待绑定的promise的代码就会得到继续执行的信号。因此,我们可以在构造promise对象时,将系统传给我们的resolve,rejct指针保存起来,在代码的其它地方,当条件满足时,再触发promise对象继续执行。

我们使用一个Websocket的例子来讨论。这个例子是通过Websocket来模拟一个远程的RPC调用,即假设远程服务器上有一个search函数:

def search(name: str):

# find user in database by given name

return ...

在javascript当中,我们希望函数是这样的

async function search(name){

let result = await ws.call({

cmd: 'search_by_name',

seq: 'daedfae038-487385afeb'

payload: {

name: 'john'

console.info(`server returns${result}`)

Javascript的websocket是异步的,而且是分两步完成收和发的运作的,因此如果不使用async/await,我们需要这样实现:

function on_search_response(result){

console.info(result)

function search(name, callback){

var ws = new WebSocket(url)

ws.send({

cmd: 'search_by_name',

seq: 'daedfae038-487385afeb',

payload: {

name: 'john'

//receive result ws.on_message = function(msg){

if (msg.data.cmd === 'search_by_name'){

callback(msg.data.payload)

这里我们又掉进了回调陷阱。而且还有一些复杂性我们没有处理,即当我们多次调用search时,服务器并不一定按客户端的调用顺序来返回,因此我们还需要在客户端发出消息前添加序号,在服务器返回结果时再换序号返回结果,这样的回调就更难写了。

现在我们的任务清楚了,我们来看看如何使用async/await以及Promise来封装一个简单的WebSocket库,以实现最简单的RPC call功能。

function WsClient (serviceUrl) {

// eventName => Set(handlers) let registry = {}

let pending_calls = {}

let connected = false

let timestamp = Date.now()

let ws = new WebSocket(serviceUrl)

ws.onmessage = function (event) {

console.debug(`Received msg:${event.data}`)

// WebSocket passing event as ... let msg = JSON.parse(event.data)

// msg now contains __seq__, name and payload if (!msg.name){

console.error(`Malformed msg${msg}`)

// handle RPC call first. RPC call is one sent by us, and wait for response. if (msg.__seq__ && pending_calls[msg.__seq__]) {

// line 1 let resolve = pending_calls[msg.__seq__].resolve

delete pending_calls[msg.__seq__]

return resolve(msg)

// call each handler let handlers = registry[msg.name]

if (handlers) {

handlers.forEach(function (func) {

func(msg)

ws.onopen = function (event) {

console.info('connected with server')

let handlers = registry['Open']

connected = true

if (handlers) {

handlers.forEach(function (handler) {

handler(event)

ws.onclose = function (event) {

console.info('disconnected with server')

let handlers = registry['Close']

connected = false

if (handlers) {

handlers.forEach(function (handler) {

handler(event)

function on (event, handler) {

/*** handler is callable(msg)* @type {*|Set}*/

let handlers = registry[event] || new Set()

handlers.add(handler)

registry[event] = handlers

function removeHandler (event, handler) {

let handlers = registry[event]

if (!handlers) {

return

handlers.delete(handler)

registry[event] = handlers

function send (msg) {

ws.send(JSON.stringify(msg))

async function call (msg) {

let __seq__ = guid()

msg.__seq__ = __seq__

// line 2 let promise = new Promise(function (resolve, reject) {

pending_calls[__seq__] = {

resolve: resolve,

reject: reject

setTimeout(function () {

delete pending_calls[__seq__]

reject(`${msg.name}:${__seq__}failed due to timeout`)

}, 20 * 1000)

ws.send(JSON.stringify(msg))

return promise

return {

on: on,

removeHandler: removeHandler,

send: send, /*send(msg)*/

call: call,/*async call(msg)*/

isConnected: function () {return connected}

代码较多,但紧要处只有两行。

一是(line 2)在ws.call被调用时,我们生成一个Promise对象,将构造Promise对象时,系统传入的resolve, reject存入pending_calls队列:

// line 2let promise = new Promise(function (resolve, reject) {

pending_calls[__seq__] = {

resolve: resolve,

reject: reject

setTimeout(function () {

//防止网络不可达或者其它错误,避免程序死等。 delete pending_calls[__seq__]

reject(`${msg.name}:${__seq__}failed due to timeout`)

}, 20 * 1000)

然后call函数返回一个未决的Promise对象,当后面我们调用await ws.cal()时,实际上就是在等待这个对象发出信号。由于resolve指针已经被保存起来了,因此,我们可以在稍后的另一个场景中,当条件满足时,来决定函数如何返回。这里有两种情况,一是如果超时后,我们reject掉这个请求;二是当on_message收到具有同样的seq的消息时,将消息内容返回,这就是line 1的作用:

if (msg.__seq__ && pending_calls[msg.__seq__]) {

// line 1 let resolve = pending_calls[msg.__seq__].resolve

delete pending_calls[msg.__seq__]

//唤醒promise对象 return resolve(msg)

现在你可以使用async/await关键字来重写你的代码,使得它们按代码顺序执行,从而有更好的可读性。async函数本质上是一个Promise,它通过Promise的resolve、reject机制来唤醒。

async函数通过await来调用,或者(既然它是一个Promise)通过then()来调用,后者主要用于async/await链的起始函数的调用。一旦在函数中使用了await关键字,函数就必须声明为async的,而且调用该函数的函数也必须声明成为async。否则,传递链将失效。

要在自己封装的库里用好async/await这一机制,就要使用外部的resolve/reject。本文给出了一个封装WebSocket以实现RPC的例子。

Javascript引擎从设计伊始就是单线程的,然而真实世界的事件往往是多头并进的。为了解决这个问题,浏览器通过UI事件、网络事件及回调函数引入了一定程度的并发执行可能(但Javascript引擎仍然是单线程的,这种并发,类似于其它语言的协程)。在复杂的程序里,回调函数是一个坑,它使得代码变得支离破碎,难以阅读。后来出现了Promise,但也没能完全解决上述问题。Javascript的最新方法是a...
赠送jar包:jboss- websocket -api_1.1_spec-2.0.0.Final.jar; 赠送原API文档:jboss- websocket -api_1.1_spec-2.0.0.Final-javadoc.jar; 赠送源代码:jboss- websocket -api_1.1_spec-2.0.0.Final-sources.jar; 赠送Maven依赖信息文件:jboss- websocket -api_1.1_spec-2.0.0.Final.pom; 包含翻译后的API文档:jboss- websocket -api_1.1_spec-2.0.0.Final-javadoc-API文档-中文(简体)-英语-对照版.zip; Maven坐标:org.jboss.spec.javax. websocket :jboss- websocket -api_1.1_spec:2.0.0.Final; 标签:jboss、spec、javax、 websocket 、api、中英对照文档、jar包、java; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内
赠送jar包:jboss- websocket -api_1.1_spec-2.0.0.Final.jar; 赠送原API文档:jboss- websocket -api_1.1_spec-2.0.0.Final-javadoc.jar; 赠送源代码:jboss- websocket -api_1.1_spec-2.0.0.Final-sources.jar; 赠送Maven依赖信息文件:jboss- websocket -api_1.1_spec-2.0.0.Final.pom; 包含翻译后的API文档:jboss- websocket -api_1.1_spec-2.0.0.Final-javadoc-API文档-中文(简体)版.zip; Maven坐标:org.jboss.spec.javax. websocket :jboss- websocket -api_1.1_spec:2.0.0.Final; 标签:jboss、spec、javax、 websocket 、api、中文文档、jar包、java; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化翻译
1.TCP协议的三次握手与四次挥手 (1)TCP连接建立过程中采用“三次握手 机制 ” 第一次握手:客户端发出连接请求报文段,其中将SYN标志位置为1表示要建立连接,选择一个初始序列号seq=x,不携带数据但消耗一个序号。之后TCP客户进程进入SYN-SENT( 同步 已发送)状态。 第二次握手:服务器收到连接请求报文段后,如同意建立连接,向客户端发送确认。在确认报文段中将SYN位和ACK位都置1,确认号是ack=x+1,选择一个初始序号seq=y,不携带数据但消耗一个序号。TCP服务器进程进入SYN-RCV JS 通过 WebSocket 实现双屏信息 同步 显示 最近项目里有个新的小需求,就是主机打开一个网页Demo,连接的安卓设备打开同一个网页Demo,两个页面是一样的,简单来说就是一个网页Demo打开两个页面A和B,在页面A中操作什么页面B中就显示什么,反之亦然。 JS 代码 <!DOCTYPE html> <title>测试网页</title> <meta http-equiv="Content-