node 异步编程
我了解到的node异步编程可分成:
1.回调函数
2.pub/sub模式(发布/订阅模式)
3.promise
4.generator
5.async await
一.直接回调函数
该方法是最直接常用的异步操作方式,比如在setInterval 和 ajax等会使用到, 存在缺点有:
1.代码不易阅读并且容易出现金字塔嵌套问题;
2.一般只能对应一个回调函数(一对一),使用上有一定的限制;
fs.readFile('/etc/passwd', function (err, data) { if (err) throw err; console.log(data); });
函数先读取/etc/passwd,再执行回调函数,在这两者之间抛出的错误会以参数形式传入回调函数中。
二.发布/订阅模式
该方法不再局限于一对一的形式,以多对多的形式监听事件,可以很方便的订阅和取消订阅存在缺点有:需要借助类库(jQuery),事件与回调函数的顺序很重要。
三. Promise
回调函数的缺点之一是容易出现函数多层嵌套,难以维护的场面.而es6语法中的promise正好解决这类问题. Promise包含三种状态:pending、fulfilled、rejected,三种状态只能发生两种转换(从pending—>fulfilled、pending—>rejected),并且状态的转换仅能发生一次。
Promise实例生成以后,可以用then方法分别指定Resolved状态和Reject状态的回调函数:
then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为Resolved时调用,第二个回调函数是Promise对象的状态变为Reject时调用。
a) then方法返回Promise。这样就实现了多个异步操作的串行操作。
b)实现了多个不同异步库之间的转换。
var p1 = new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('fail')), 3000) }) var p2 = new Promise(function (resolve, reject) { setTimeout(() => resolve(p1), 1000) }) p2.then(result => console.log(result)) p2.catch(error => console.log(error)) // Error: fail
Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。是我们项目中正在使用的方式。如var p = Promise.all([p1,p2,p3]);
p的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
四.Generator
Generator最大的特点就是可以实现函数的暂停、重启,这个特性非常有利于解决异步操作有两个名词需要注意yield 和 next。
- generator的解决机制有点像线程.next方法的作用是分阶段执行Generator函数。每次调用next方法,会返回一个对象,表示当前阶段的信息(value属性和done属性)。value属性是yield语句后面表达式的值,表示当前阶段的值;done属性是一个布尔值,表示Generator函数是否执行完毕,即是否还有下一个阶段。
- yield 命令是标志异步操作的标志,如果去掉此命令,函数就会像执行同步函数那样执行就暂停,等到执行权返回,再从暂停的地方继续往后执行。
Generator 函数的还提供了数据交换和错误处理机制。其函数内部还可以部署错误处理代码,捕获函数体外抛出的错误。
function* gen(x){ try { var y = yield x + 2; } catch (e){ console.log(e); } return y; } var g = gen(1); g.next(); g.throw('出错了'); // 出错了
除此之外,generator函数还有两个小帮手Thunk函数(自动控制Generator函数的流程,接收和交还程序的执行权的一种机制)和co模块(用于Generator函数的自动执行。)
五.async await
ES7提供了async函数,成为generator函数的语法糖,它主要就是用async来说代替*,用await来代替yield,除此之外,他还带来了一些便利之处:
1.generator函数的调用需要借助next方法或者是co模块,而async和普通函数的调用一样,不需要借助任何函数;
2.co模块约定,yield命令后面只能是Thunk函数或Promise对象,而async函数的await命令后面,可以是Promise对象和原始类型的值
注意的是: await 命令只能用在 async 函数之中,如果用在普通函数,就会报错。
async function dbFuc(db) { let docs = [{}, {}, {}]; // 报错 docs.forEach(function (doc) { await db.post(doc); }); }
上面代码会报错,因为 await 用在普通函数之中了。但是,如果将 forEach 方法的参数改成 async 函数,也有问题。
async function dbFuc(db) { let docs = [{}, {}, {}]; // 可能得到错误结果 docs.forEach(async function (doc) { await db.post(doc); }); }
上面代码可能不会正常工作,原因是这时三个 db.post 操作将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采用 for 循环。
async function dbFuc(db) { let docs = [{}, {}, {}]; for (let doc of docs) { await db.post(doc); } }
如果确实希望多个请求并发执行,可以使用 Promise.all 方法。
async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = await Promise.all(promises); console.log(results); } // 或者使用下面的写法 async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = []; for (let promise of promises) { results.push(await promise); } console.log(results); }
内容涉及的不是很深入,后续会继续补充和修改,希望大家多多指正