所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。
为什么 Node.js 约定,回调函数的第一个参数,必须是错误对象err(如果没有错误,该参数就是 null)?原因是执行分成两段,在这两段之间抛出的错误,程序无法捕捉,只能当作参数,传入第二段。
fs.readFile('/etc/passwd', function (err, data) {
if (err) throw err;
console.log(data);
});
回调函数本身并没有问题,它的问题出现在多个回调函数嵌套。难想象,如果依次读取多个文件,就会出现多重嵌套。代码不是纵向发展,而是横向发展,很快就会乱成一团,无法管理。回调地狱其实就是这个:
doSomethingAsync1(function(){
4 doSomethingAsync2(function(){
5 doSomethingAsync3(function(){
6 doSomethingAsync4(function(){
7 doSomethingAsync5(function(){
一旦嵌套层数加深,就会出现“回调金字塔”的问题,就像example 4那样,如果这里面的每个回调函数中又包含了很多业务逻辑的话,整个代码块就会变得非常复杂。从逻辑正确性的角度来说,上面这几种回调函数的写法没有任何问题,但是随着业务逻辑的增加和趋于复杂,这种写法的缺点马上就会暴露出来,想要维护它们实在是太痛苦了,这就是“回调地狱(callback hell)”。
异步代码回调函数中的异常无法被外层的try/catch语句捕获。
Promise就是为了解决这个问题而提出的。它不是新的语法功能,而是一种新的写法,允许将回调函数的横向加载,改成纵向加载。采用Promise,连续读取多个文件,它会立即返回,但是返回的东西必须调用.then才会有最终结果。
var readFile = require('fs-readfile-promise');
readFile(fileA)
.then(function(data){
console.log(data.toString());
})
.then(function(){
return readFile(fileB);
})
.then(function(data){
console.log(data.toString());
})
.catch(function(err) {
console.log(err);
});
function read(filename){
// 相当于把异步函数包裹在Promise中
var promise = new Promise(function(resolve, reject){
fs.readFile(filename, 'utf8', function(err, data){
if (err){
reject(err);
}
resolve(data);
})
});
return promise;
}
这里的两个function分别就是resolve和reject,非常直观,很不错。怎么说呢?这段代码不能按照常理思考,这个new Promise操作是会执行到里面的,同时return。。。异步执行的,resove和reject就相当于异步的return了,就是如此。用的时候就then,对吧。
Promise 的最大问题是代码冗余,原来的任务被Promise 包装了一下,不管什么操作,一眼看去都是一堆 then,原来的语义变得很不清楚。
gengator的yelid就是return,只不过它是中间阶段的return。
{
function* gen() {
yield 'hello'
yield 'world'
return 'ending'
}
let it = gen()
it.next() // {value: "hello", done: false}
it.next() // {value: "world", done: false}
it.next() // {value: "ending", done: true}
it.next() // {value: undefined, done: true}
只不过返回的也不是输出什么,而是包装输出,但还是输出。
一个函数只能执行一次 return 语句,而在 Generator 函数中可以有任意多个 yield
如果在 Generator 函数里面调用另一个 Generator 函数,默认情况下是没有效果的
由于 Generator函数是 “惰性求值”,执行到第一个 yield 时才会计算求和,并加计算结果返回给遍历器对象 {value: 14, done: false}
也就是说,把异步这个定义转化为了中断!
不是说取代异步,而是说管理异步。async是属于es7的。转码器 Babel 和 regenerator 都已经支持,转码后就能使用。
只有一个await是没有必要的,因为迭代这么多只是管理多个异步而已,单纯一个异步是不需要什么解决方案的。
promise是单个异步解决方案,对呀,要说异步,直接传一个函数就行了呀。
传参异步->promise->async
无论有没有await,async返回的永远都是promise,如果在函数中 return
一个直接量,async 会把这个直接量通过 Promise.resolve()
封装成 Promise 对象。
Promise.resolve(x)
可以看作是 new Promise(resolve => resolve(x))
的简写,可以用于快速封装字面量对象或其他对象,将其封装成 Promise 实例。
现在回过头来想下,如果 async 函数没有返回值,又该如何?很容易想到,它会返回 Promise.resolve(undefined)
。
反正最后都有then,有什么意义呢?举个例子,例如依次进行增删查改4个异步操作。它会更方便么?
async zscg(){
await zeng();
await shan();
await cha();
await gai();
}
注意这几个await是依次执行的,如果返回什么东西,就在前面输出好了,简直太棒了,根本不用输出什么,因为有什么操作就直接在里面实现了!如果真要返回一个东西,那也是个promise,调用.then就好了!
其实promise并不是一无是处,它最重要的就是包装另一个promise,返回加工过的结果!现在老是用async,都不会用promise了。
执行的大意:
同步环境执行(step1) -> 事件循环1(step4) -> 事件循环2(step4的重复)…
也就是说,异步函数需要等到所有同步代码执行完毕后才会执行。如果乱给函数加异步,就很有可能导致加载过程颠倒混乱,就是如此。
Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
Promse.all在处理多个异步处理时非常有用,比如说一个页面上需要等两个或多个ajax的数据回来以后才正常显示,在此之前只显示loading图标。
需要特别注意的是,Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,即p1的结果在前,即便p1的结果获取的比p2要晚。这带来了一个绝大的好处:在前端开发请求数据的过程中,偶尔会遇到发送多个请求并根据请求顺序获取和使用数据的场景,使用Promise.all毫无疑问可以解决这个问题。
Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
原理是挺简单的,但是在实际运用中还没有想到什么的使用场景会使用到。