node 異步編程

node 異步編程

我瞭解到的node異步編程可分紅: 
1.回調函數 
2.pub/sub模式(發佈/訂閱模式) 
3.promise 
4.generator 
5.async awaitjavascript

一.直接回調函數

該方法是最直接經常使用的異步操做方式,好比在setInterval 和 ajax等會使用到, 存在缺點有: 
1.代碼不易閱讀而且容易出現金字塔嵌套問題; 
2.通常只能對應一個回調函數(一對一),使用上有必定的限制;java

fs.readFile('/etc/passwd', function (err, data) {
  if (err) throw err;
  console.log(data);
});

函數先讀取/etc/passwd,再執行回調函數,在這二者之間拋出的錯誤會以參數形式傳入回調函數中。node

二.發佈/訂閱模式

該方法再也不侷限於一對一的形式,以多對多的形式監聽事件,能夠很方便的訂閱和取消訂閱存在缺點有:須要藉助類庫(jQuery),事件與回調函數的順序很重要。es6

三. Promise

回調函數的缺點之一是容易出現函數多層嵌套,難以維護的場面.而es6語法中的promise正好解決這類問題. Promise包含三種狀態:pending、fulfilled、rejected,三種狀態只能發生兩種轉換(從pending—>fulfilled、pending—>rejected),而且狀態的轉換僅能發生一次。 
Promise實例生成之後,能夠用then方法分別指定Resolved狀態和Reject狀態的回調函數: 
then方法能夠接受兩個回調函數做爲參數。第一個回調函數是Promise對象的狀態變爲Resolved時調用,第二個回調函數是Promise對象的狀態變爲Reject時調用。 
a) then方法返回Promise。這樣就實現了多個異步操做的串行操做。 
b)實現了多個不一樣異步庫之間的轉換。ajax

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實例。是咱們項目中正在使用的方式。如p的狀態由p一、p二、p3決定,分紅兩種狀況。var p = Promise.all([p1,p2,p3]);

(1)只有p一、p二、p3的狀態都變成fulfilled,p的狀態纔會變成fulfilled,此時p一、p二、p3的返回值組成一個數組,傳遞給p的回調函數。編程

(2)只要p一、p二、p3之中有一個被rejected,p的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。數組

四.Generator

Generator最大的特色就是能夠實現函數的暫停、重啓,這個特性很是有利於解決異步操做有兩個名詞須要注意yield 和 next。promise

  1. generator的解決機制有點像線程.next方法的做用是分階段執行Generator函數。每次調用next方法,會返回一個對象,表示當前階段的信息(value屬性和done屬性)。value屬性是yield語句後面表達式的值,表示當前階段的值;done屬性是一個布爾值,表示Generator函數是否執行完畢,便是否還有下一個階段。
  2. 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);
}
內容涉及的不是很深刻,後續會繼續補充和修改,但願你們多多指正
相關文章
相關標籤/搜索