起首明白一个题目,为何 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,而且在将来某一个时候被挪用。callback的异步情势最大的题目就是,明白难题加回调地狱(callback hell),看下面的代码的实行递次:
A();
ajax('url1', function(){
B();
ajax('url2', function(){
C();
}
D();
});
E();
实在行递次为:A => E => B => D => C
,这类实行递次确实会让人思想发昏,别的因为因为多个异步操纵之间每每会耦合,只需中心一个操纵须要修正,那末它的上层回调函数和基层回调函数都可能要修正,这就陷入了回调地狱。而 Promise 对象就很好的处理了异步操纵之间的耦合题目,让我们能够用同步编程的体式格局去写异步操纵。
Promise
Promise
对象是一个组织函数,用来天生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
状况的回调函数,而这两个回调函数接收的参数分别是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
要领只要在一切同步使命实行完后才会实行,看看下面的例子:
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 要领
then
要领会返回一个新的 Promise 实例,能够分两种状况来看:
- 指定返回值是新的 Promise 对象,如
return new Promise(...)
,这类状况没啥好说的,因为返回的是Promise
,背面明显能够继承挪用then
要领。 - 返回值不是Promise, 如:
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
async
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
望文生义, 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强在哪儿?
接下来我们用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…