JavaScript非同步發展史

_shine發表於2018-01-19

JavaScript 的所有網路操作,瀏覽器事件,都必須是非同步執行。 正如我們所知道的那樣,在JavaScript中,非同步程式設計方式只能通過JavaScript語言中的一等公民函式才能完成:這種方式意味著我們可以將一個函式作為另一個函式的引數,在這個函式的內部可以呼叫被傳遞進來的函式(即回撥函式)。

let fs = require('fs');
 fs.readFile('./1.txt','utf8',function(err,data){
 if(err){//如果err有值,就表示程式出錯了
 console.log(err);
 }else{//如果error為空,就表示 成功了,沒有錯誤
 console.log(data);
 }
 })
複製程式碼

回撥函式的問題

    1. 無法捕獲錯誤 try catch
    1. 不能return
    1. 回撥地獄
function read(filename){
 fs.readFile(filename,'utf8',function(err,data){
 throw Error('出錯了')
 if(err){//如果err有值,就表示程式出錯了
 console.log(err);
 }else{//如果error為空,就表示 成功了,沒有錯誤
 console.log(data);
 }
 })
 }
 try{
 read('./1.txt');
 }catch(e){
 console.log('err',e);
 };
複製程式碼

當你訪問伺服器的時候,比如要請求一個HTML頁面,比如是使用者列表。伺服器一方面會去讀取讀模板檔案,可能是ejs pug jade handlebar ,另外一方面還要讀取資料(可能會放在檔案裡,也可以會放在資料裡),它們都很慢,所以都是非同步的。

這種惡魔金字塔有以下問題

    1. 非常難看
    1. 非常難以維護
    1. 效率比較低,因為它們是序列的
fs.readFile('./template.txt', 'utf8', function (err, template) {
  fs.readFile('./data.txt', 'utf8', function (err, data) {
    console.log(template + ' ' + data);
  })
})
複製程式碼

如何解決這個回撥巢狀的問題

1.通過事件釋出訂閱來實現 這是node核心模組中的一個類,通過它可以建立事件發射器的例項,裡面有兩個核心方法,一個叫on emit,on表示註冊監聽,emit表示發射事件

let EventEmitter = require('events');
let eve = new EventEmitter();
//這個html物件是存放最終資料
let html = {};//template data
//監聽資料獲取成功事件,當事件發生之後呼叫回撥函式
eve.on('ready',function(key,value){
  html[key] = value;
  if(Object.keys(html).length == 2){
    console.log(html);
  }
});
fs.readFile('./template.txt', 'utf8', function (err, template) {
  //1事件名 2引數往後是傳遞給回撥函式的引數
  eve.emit('ready','template',template);
})
fs.readFile('./data.txt', 'utf8', function (err, data) {
  eve.emit('ready','data',data);
})*/
//通過一個哨兵函式來處理
function done(key,value){
  html[key] = value;
  if(Object.keys(html).length == 2){
    console.log(html);
  }
}
複製程式碼

ES6的很多特性都跟Generator扯上關係,而且實際用處比較廣, 包含了任何需要非同步的模組, 比如ajax, filesystem, 或者陣列物件遍歷等都可以用到; Generator函式和普通的函式區別有兩個, 1:function和函式名之間有一個*號, 2:函式體內部使用了yield表示式;比如這樣:

/**
 * 生成器是一個函式,可以用來生成迭代器
 * 生成器函式和普通函式不一樣,普通函式是一旦呼叫一定會執行完
 * 但是生成器函式中間可以暫停,可以執行一會歇一會
 */
//生成器函式有一個特點,需要加個*
//生成器有若干個階段,如何劃分這些階段呢?
function *go(a){
    console.log(1);
    //此處的b用來供外界輸入進來的
    //這一行實現輸入和輸出,本次的輸出放在yield後面,下次的輸入放在yield前面
    let b =  yield a;
    console.log(2);
    let c = yield b;
    console.log(3);
    return c;
}
//生成器函式和普通的函式不一樣,呼叫它的話函式並不會立刻執行
//它會返回此生成器的迭代器,迭代器是一個物件,每呼叫一次next就可以返回一個值物件
let it = go("a值");
//next第一次執行不需要引數,傳引數沒有意義
let r1 = it.next();
//第一次呼叫next會返回一個物件,此物件有兩個屬性,一個是value就是yield後面那個值,一個是done表示是否迭代完成
console.log(r1);//{ value: 'a', done: false }
let r2 = it.next('B值');
console.log(r2);//{ value: 'B值', done: false }
let r3 = it.next('C值');
console.log(r3);//{ value: 'C值', done: true }
複製程式碼

!重點來了

先回憶之前promise對非同步的實現,以 bluebird為例:

let Promise = require('bluebird');
let fs = require('fs');
function promisifyAll(obj) {
  for (let key in obj) {
    if (obj.hasOwnProperty(key) && typeof obj[key] == 'function') {
      obj[key+'Async'] = Promise.promisify(obj[key]);
    }
  }
}
//它會遍歷物件上面的所有方法,然後對每個方法新增一個新的方法 Async
promisifyAll(fs);
fs.readFileAsync('./1.txt', 'utf8').then(data => console.log(data));
複製程式碼

現在將Generator與promise綜合在一起:

let fs = require('fs');
function readFile(filename) {
 return new Promise(function (resolve, reject) {
   fs.readFile(filename, 'utf8', function (err, data) {
     err ? reject(err) : resolve(data);
   });
 })
}
function *read() {
 console.log('開始');
 let a = yield readFile('1.txt');
 console.log(a);
 let b = yield readFile('2.txt');
 console.log(b);
 let c = yield readFile('3.txt');
 console.log(c);
 return c;
}
function co(gen) {
 let it = gen();//我們要讓我們的生成器持續執行
 return new Promise(function (resolve, reject) {
   !function next(lastVal) {
       let {value,done} = it.next(lastVal);
       if(done){
         resolve(value);
       }else{
         value.then(next,reject);
       }
   }()
 });
}
co(read).then(function (data) {
 console.log(data);
});
複製程式碼

隨著 Node 7的釋出,越來越多的人開始研究據說是非同步程式設計終級解決方案的 async/await

let Promise = require('bluebird');
let readFile = Promise.promisify(require('fs').readFile);
async function read() {
  //await後面必須跟一個promise,
  let a = await readFile('./1.txt','utf8');
  console.log(a);
  let b = await readFile('./2.txt','utf8');
  console.log(b);
  let c = await readFile('./3.txt','utf8');
  console.log(c);
  return 'ok';
}

read().then(data => {
  console.log(data);
});
複製程式碼

async await是語法糖,內部還是用generator+promise實現

相關文章