,首先要明确一个问题,为什么Node.js需要异步编程?JavaScript是单线程的。当调用时,直到得到结果后调用才会返回,也就是说调用者主动等待调用的结果。换句话说,它必须等待上一个任务执行完才能执行下一个任务,这种执行方式称为:同步。Node.js的主要应用场景是处理高并发(单位时间内访问量很大)和I/O密集型场景(ps:I/O操作往往非常耗时,所以异步的关键是解决I/O耗时问题),如果使用同步编程,问题就来了。服务器处理一次I/O请求需要花费大量时间,后续请求会排队,导致浏览器卡顿。异步编程可以解决这个问题。所谓异步就是调用发出后,调用直接返回。调用者不会立即得到结果,但不会阻塞,可以继续进行后续操作,被调用者得到结果后会通过状态和事件通知调用。或者使用回调函数(callback)来处理结果。Node在处理耗时的I/O操作时,将其交给其他线程处理,自己继续处理其他访问请求。当I/O操作处理完成后,会通过事件通知Node回调,进行后续处理。有一个很好的例子:你打电话给书店老板问他有没有书《分布式系统》,如果是同步通讯机制,书店老板会说,等一下,“我查一下”,然后开始查查查,等查好了(可能5秒,可能一天)告诉你结果(返回结果)。至于异步通信机制,书店老板直接跟你说我查,查完我给你打电话,然后直接挂断(不返回结果)。然后查一下,他就会主动给你打电话。这里老板用“回电”来回电。以下方法是异步解决方案的演变过程:CallBacks回调函数是函数A作为参数传递给函数B,在以后的某个时间被调用。回调的异步方式最大的问题是难以理解和回调地狱(callbackhell),看下面代码的执行顺序:A();ajax('url1',function(){B();ajax('url2',function(){C();}D();});E();它的执行顺序是:A=>E=>B=>D=>C,这个执行顺序会确实让人头晕,另外由于多个异步操作之间的耦合,只要修改中间的一个操作,它的上层回调函数和下层回调函数就可能需要修改,从而导致到一个回调地狱。Promise对象很好的解决了异步操作之间的耦合问题,这样我们就可以用同步编程来写异步操作。PromisePromise对象是一个构造函数,用来生成promise实例。Promise代表一个异步操作,有三个状态:pending,resolved(异步操作成功由pending变为resolved),rejected(异步操作失败由pending变为rejected),一旦变成后两种状态,就不会再变了。Promise对象接受一个函数作为构造函数的参数,而这个函数接受resolve和reject两个函数作为参数。这两个函数是JS内置的,不需要配置。异步操作成功后调用resolve函数,将pending状态变为resolved,并将其参数传递给回调函数;reject函数在异步操作失败时被调用,将pending状态更改为rejected,并将参数传递给回调函数。Promise.prototype.then()Promise构造函数的原型上有一个then方法,它接受两个函数作为参数,分别是resolved状态和rejected状态的回调函数,这两个callback接受的参数functions分别是Promise实例中resolve函数和reject函数的参数。另外,拒绝状态的回调函数可以省略。这是一个示例用法:constinstance=newPromise((resolve,reject)=>{//一些异步操作if(/*asyncoperationsucceeded*/){resolve(value);}else{reject(error);}}})instance.then(value=>{//dosomething...},error=>{//dosomething...})注意Promise实例会在生成后立即执行,而then方法只会在所有同步任务执行完毕后执行任务。看看下面的例子:constpromise=newPromise((resolve,reject)=>{console.log('asynctaskbegins!');setTimeout(()=>{resolve('done,pending->resolved!');},1000);})promise.then(value=>{console.log(value);})console.log('1.pleasewait');console.log('2.pleasewait');console.log('3.pleasewait');//异步任务开始!//1.pleasewait//2.pleasewait//3.pleasewait//done,pending->resolved!从上面的例子可以看出,Promise实例生成后立即执行,所以先输出'asynctaskbegins!',然后定义一个异步操作setTimeout,1秒后执行,所以没有需要等待向下执行,而then方法指定的回调函数会在所有同步任务执行完毕后执行,所以先输出三个'pleasewait',最后输出'done,pending->resolved!'是输出。(这里省略了then方法中的reject回调,一般then中不处理rejected状态,而是使用catch方法专门处理错误,相当于.then(null,reject))链式调用thenmethodthen方法会返回一个新的Promise实例,可以分为两种情况来看:指定返回值是一个新的Promise对象,比如returnnewPromise(...),这种情况没什么好说的,因为return是一个Promise,显然后面可以继续调用then方法。返回值不是Promise,如:return1这种情况下,还是会返回一个Promise,Promise会立即执行回调resolve(1)。所以then方法还是可以链式调用的。(注意:如果没有指定return语句,相当于返回undefined)使用then的链式写法,依次实现一系列的异步操作,这样就可以以同步编程的形式实现异步操作。请参见以下示例。每两秒打个招呼:functionsayHi(name){returnnewPromise((resolve,reject)=>{setTimeout(()=>{resolve(name);},2000)})}sayHi('张三').then(name=>{console.log(`Hello,${name}`);returnsayHi('李四');//最后resolved函数中的参数会作为值传给下一个then})//name是上次传入的参数then.then(name=>{console.log(`Hello,${name}`);returnsayHi('王二麻子');}).then(name=>{console.log(`Hello,${name}`);})//你好,张三//你好,李四//你好,王二麻子可以看到使用chainedthen写法改成异步操作变成了同步形式,但是也带来了新的问题,就是异步操作变成了一个很长的then链。新的解决方案是Generator,这里略过,直接讲它的语法糖:async/await。async/awaitasyncasync/await其实就是Generator的语法糖。顾名思义,async关键字表示后面的函数中有一个异步操作,await表示等待一个异步方法完成。声明一个异步函数,只需要在普通函数前面加上关键字async,如:asyncfunctionfuncA(){}async函数返回一个Promise对象(如果指定的返回值不是Promise对象,同样返回一个Promise,但是立即resolve,处理方式同then方法),所以async函数通过return返回的值会成为then方法中回调函数的参数:asyncfunctionfuncA(){return'hello!';}funcA().then(value=>{console.log(value);})//你好!单个异步函数实际上执行与Promise相同的功能。让我们看看await做了什么。await顾名思义就是异步等待。它等待一个Promise,所以在await之后应该写一个Promise对象。如果它不是一个Promise对象,它将被转换为一个立即resolve的Promise。async函数被调用后立即执行,但是一旦遇到await,它会先返回,等到异步操作执行完毕,再执行函数体中后面的语句。总结一下:async函数调用不会造成代码阻塞,但是await会导致async函数内部代码阻塞。看看这个例子:asyncfunctionfunc(){console.log('asyncfunctionisrunning!');常数num1=等待200;console.log(`num1是${num1}`);constnum2=awaitnum1+100;console.log(`num2是${num2}`);constnum3=awaitnum2+100;console.log(`num3is${num3}`);}func();console.log('runmebeforeawait!');//asyncfunctionisrunning!//runmebeforeawait!//num1is200//num2is300//num3is400可以看出,调用asyncfunc函数后,会立即执行,先输出'asyncfunctionisrunning!',然后遇到await异步等待,函数返回,先在func()之后执行同步任务,同步任务执行完后,在等待位置继续执行。可以说async函数可以看成是多个异步操作封装到一个Promise对象中,而await命令就是内部then命令的语法糖。值得注意的是,await之后的Promise对象并不总是返回到resolved状态。只要一个await之后的Promise状态变为rejected,整个async函数的执行就会被打断。为了保存错误位置和错误信息,我们需要使用try...catch语句来封装多个await进程,如下:asyncfunctionfunc(){try{constnum1=await200;console.log(`num1是${num1}`);constnum2=awaitPromise.reject('num2是错误的!');console.log(`num2是${num2}`);constnum3=awaitnum2+100;console.log(`num3是${num3}`);}赶上(错误){控制台。日志(错误);}}func();//num1是200//发生错误//num2是错误的!如上所示,num2处的await得到一个状态为rejected的Promise对象,错误会传递给catch语句,这样我们就可以定位错误发生的位置。async/await比Promise好在哪里?接下来我们用async/await重写一个Promise章节中关于sayHi的例子。代码如下:functionsayHi(name){returnnewPromise((resolved,rejected)=>{setTimeout(()=>{resolved(name);},2000)})}asyncfunctionsayHi_async(name){constsayHi_1=awaitsayHi(name)console.log(`Hi,${sayHi_1}`)constsayHi_2=awaitsayHi('Lisi')控制台。log(`Hello,${sayHi_2}`)constsayHi_3=awaitsayHi('王二麻子')console.log(`Hello,${sayHi_3}`)}sayHi_async('张三')//你好,张三//你好,李四//你好,王二麻子相对于之前长长的then链和then方法中的回调函数,这种写法看起来更像是一种同步的写法,更加清爽,更符合编程习惯.参考文章https://segmentfault.com/a/11...https://segmentfault.com/a/11...https://www.zhihu.com/questio...
