首先明确一个问题,为何 Node.js
须要异步编程?ajax
JavaScript
是单线程的,在发出一个调用时,在没有获得结果以前,该调用就不返回,意思就是调用者主动等待调用结果,换句话说,就是必须等待上一个任务执行完才能执行下一个任务,这种执行模式叫:同步。Node.js
的主要应用场景是处理高并发(单位时间内极大的访问量)和 I/O
密集场景(ps: I/O
操做每每很是耗时,因此异步的关键在于解决 I/O
耗时问题),若是采用同步编程,问题就来了,服务器处理一个 I/O
请求须要大量的时间,后面的请求都将排队,形成浏览器端的卡顿。异步编程能解决这个问题。
所谓异步,就是调用在发出后,这个调用就直接返回了,调用者不会当即获得结果,可是不会阻塞,能够继续执行后续操做,而被调用者执行获得结果后经过状态、事件来通知调用者使用回调函数( callback
)来处理这个结果。Node在处理耗时的 I/O
操做时,将其交给其余线程处理,本身继续处理其余访问请求,当 I/O
操做处理好后就会经过事件通知 Node 用回调作后续处理。
有个例子很是好:编程
你打电话问书店老板有没有《分布式系统》这本书,若是是同步通讯机制,书店老板会说,你稍等,”我查一下",而后开始查啊查,等查好了(多是5秒,也多是一天)告诉你结果(返回结果)。而异步通讯机制,书店老板直接告诉你我查一下啊,查好了打电话给你,而后直接挂电话了(不返回结果)。而后查好了,他会主动打电话给你。在这里老板经过“回电”这种方式来回调。
下面几种方式是异步解决方案的进化过程:segmentfault
回调函数就是函数A做为参数传递给函数B,而且在将来某一个时间被调用。callback的异步模式最大的问题就是,理解困难加回调地狱(callback hell),看下面的代码的执行顺序:promise
A(); ajax('url1', function(){ B(); ajax('url2', function(){ C(); } D(); }); E();
其执行顺序为:A => E => B => D => C
,这种执行顺序的确会让人头脑发昏,另外因为因为多个异步操做之间每每会耦合,只要中间一个操做须要修改,那么它的上层回调函数和下层回调函数均可能要修改,这就陷入了回调地狱。而 Promise 对象就很好的解决了异步操做之间的耦合问题,让咱们能够用同步编程的方式去写异步操做。浏览器
Promise
对象是一个构造函数,用来生成promise
实例。Promise
表明一个异步操做,有三种状态:pending,resolved
(异步操做成功由 pending
变为 resolved
),rejected
(异步操做失败由 pending
变为 rejected
),一旦变为后两种状态将不会再改变。Promise
对象做为构造函数接受一个函数做为参数,而这个函数又接受 resolve
和 reject
两个函数作为参数,这两个函数是JS内置的,无需配置。resolve
函数在异步操做成功后调用,将pending
状态变为resolved
,并将它的参数传递给回调函数;reject
函数在异步操做失败时调用,将pending
状态变为rejected
,并将参数传递给回调函数。服务器
Promise
构造函数的原型上有一个then
方法,它接受两个函数做为参数,分别是 resolved
状态和 rejected
状态的回调函数,而这两个回调函数接受的参数分别是Promise
实例中resolve
函数和reject
函数中的参数。 另外rejected
状态的回调函数是可省略的。并发
下面是一个使用示例:异步
const instance = new Promise((resolve, reject) => { // 一些异步操做 if(/*异步操做成功*/) { resolve(value); } else { reject(error); } } }) instance.then(value => { // do something... }, error => { // do something... })
注意Promise实例在生成后会当即执行,而 then
方法只有在全部同步任务执行完后才会执行,看看下面的例子:async
const promise = new Promise((resolve, reject) => { console.log('async task begins!'); setTimeout(() => { resolve('done, pending -> resolved!'); }, 1000); }) promise.then(value => { console.log(value); }) console.log('1.please wait'); console.log('2.please wait'); console.log('3.please wait'); // async task begins! // 1.please wait // 2.please wait // 3.please wait // done, pending -> resolved!
上面的实例能够看出,Promise实例生成后当即执行,因此首先输出 'async task begins!'
,随后定义了一个异步操做 setTimeout
,1秒后执行,因此无需等待,向下执行,而then
方法指定的回调函数要在全部同步任务执行完后才执行,因此先输出了3个'please wait'
,最后输出'done, pending -> resolved!'
。(此处省略了then
方法中的reject
回调,通常不在then
中作rejected
状态的处理,而使用catch
方法专门处理错误,至关于.then(null, reject)
)分布式
then
方法会返回一个新的 Promise 实例,能够分两种状况来看:
return new Promise(...)
,这种状况没啥好说的,因为返回的是 Promise
,后面显然能够继续调用then
方法。return 1
这种状况仍是会返回一个 Promise
,而且这个Promise
当即执行回调 resolve(1)
。因此仍然能够链式调用then
方法。(注:若是没有指定return
语句,至关于返回了undefined
)使用 then 的链式写法,按顺序实现一系列的异步操做,这样就能够用同步编程的形式去实现异步操做,来看下面的例子,实现隔两秒打一次招呼:
function sayHi(name) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(name); }, 2000) }) } sayHi('张三') .then(name => { console.log(`你好, ${name}`); return sayHi('李四'); // 最终 resolved 函数中的参数将做为值传递给下一个then }) // name 是上一个then传递出来的参数 .then(name => { console.log(`你好, ${name}`); return sayHi('王二麻子'); }) .then(name => { console.log(`你好, ${name}`); }) // 你好, 张三 // 你好, 李四 // 你好, 王二麻子
能够看到使用链式then的写法,将异步操做变成了同步的形式,可是也带来了新的问题,就是异步操做变成了很长的then链,新的解决方法就是Generator
,这里跨过它直接说它的语法糖:async/await
。
async/await
其实是Generator
的语法糖。顾名思义,async
关键字表明后面的函数中有异步操做,await
表示等待一个异步方法执行完成。声明异步函数只需在普通函数前面加一个关键字async
便可,如:
async function funcA() {}
async
函数返回一个Promise对象(若是指定的返回值不是Promise对象,也返回一个Promise,只不过当即 resolve
,处理方式同 then
方法),所以 async
函数经过 return
返回的值,会成为 then
方法中回调函数的参数:
async function funcA() { return 'hello!'; } funcA().then(value => { console.log(value); }) // hello!
单独一个 async
函数,其实与Promise执行的功能是同样的,来看看 await
都干了些啥。
顾名思义, await
就是异步等待,它等待的是一个Promise,所以 await
后面应该写一个Promise对象,若是不是Promise对象,那么会被转成一个当即 resolve
的Promise。 async
函数被调用后就当即执行,可是一旦遇到 await
就会先返回,等到异步操做执行完成,再接着执行函数体内后面的语句。总结一下就是:async
函数调用不会形成代码的阻塞,可是await
会引发async
函数内部代码的阻塞。看看下面这个例子:
async function func() { console.log('async function is running!'); const num1 = await 200; console.log(`num1 is ${num1}`); const num2 = await num1+ 100; console.log(`num2 is ${num2}`); const num3 = await num2 + 100; console.log(`num3 is ${num3}`); } func(); console.log('run me before await!'); // async function is running! // run me before await! // num1 is 200 // num2 is 300 // num3 is 400
能够看出调用 async func
函数后,它会当即执行,首先输出了'async function is running!'
,接着遇到了 await
异步等待,函数返回,先执行func()
后面的同步任务,同步任务执行完后,接着await等待的位置继续往下执行。能够说,async
函数彻底能够看做多个异步操做,包装成的一个Promise 对象,而await
命令就是内部then
命令的语法糖。
值得注意的是, await
后面的 Promise 对象不老是返回 resolved
状态,只要一个 await
后面的Promise状态变为 rejected
,整个 async
函数都会中断执行,为了保存错误的位置和错误信息,咱们须要用 try...catch
语句来封装多个 await
过程,以下:
async function func() { try { const num1 = await 200; console.log(`num1 is ${num1}`); const num2 = await Promise.reject('num2 is wrong!'); console.log(`num2 is ${num2}`); const num3 = await num2 + 100; console.log(`num3 is ${num3}`); } catch (error) { console.log(error); } } func(); // num1 is 200 // 出错了 // num2 is wrong!
如上所示,在 num2
处 await
获得了一个状态为 rejected
的Promise对象,该错误会被传递到 catch
语句中,这样咱们就能够定位错误发生的位置。
接下来咱们用async/await
改写一下Promise章节中关于sayHi
的一个例子,代码以下:
function sayHi(name) { return new Promise((resolved, rejected) => { setTimeout(() => { resolved(name); }, 2000) }) } async function sayHi_async(name) { const sayHi_1 = await sayHi(name) console.log(`你好, ${sayHi_1}`) const sayHi_2 = await sayHi('李四') console.log(`你好, ${sayHi_2}`) const sayHi_3 = await sayHi('王二麻子') console.log(`你好, ${sayHi_3}`) } sayHi_async('张三') // 你好, 张三 // 你好, 李四 // 你好, 王二麻子
与以前长长的then链和then方法里的回调函数相比,这样的写法看起来像是同步写法而且更加清爽,更加符合编程习惯。
https://segmentfault.com/a/11...
https://segmentfault.com/a/11...
https://www.zhihu.com/questio...