1. 程式人生 > >JavaScript從初級往高階走系列————非同步

JavaScript從初級往高階走系列————非同步

非同步

  • 什麼是單執行緒,和非同步有什麼關係
  • 什麼是event-loop
  • 是否用過jQuery的Deferred
  • Promise的基本使用和原理
  • 介紹一下async/await(和Promise的區別、聯絡)
  • 非同步解決方案

什麼是單執行緒,和非同步有什麼關係

單執行緒-只有一個執行緒,只做一件事。JS之所以是單執行緒,取決於它的實際使用,例如JS不可能同新增一個DOM和刪除這個DOM,所以它只能是單執行緒的。
console.log(1);
alert(1);
console.log(2);

上面這個例子中,當執行了alert(1),如果使用者不點選確定按鈕,console.log(2)是不會執行的。

為了利用多核CPU的計算能力,HTML5提出WebWorker標準,允許JavaScript指令碼建立多個執行緒,但是子執行緒完全受主執行緒控制,且不得操作DOM。所以,這個新標準並沒有改變JavaScript單執行緒的本質。

js非同步

console.log(100);
setTimeout(function(){
  console.log(200);
},1000)
console.log(300);
console.log(400);
console.log(400);
.... // 這裡來很多很多個console.log(400); 結果就是列印完所有的400,等一秒再列印200

event-loop

event-loop

文字解釋

  • 事件輪詢,JS實現非同步的具體解決方案
  • 同步程式碼,直接執行
  • 非同步函式先放在非同步佇列中
  • 待同步函式執行完畢,輪詢執行 非同步佇列 的函式

上面那個例子的執行效果就是這樣的:

例項分析:

這個例子中有兩種情況,取決於ajax的返回時間,如果ajax時間小於100ms它就先放進非同步佇列

Jquery Deferred

Jquery1.5前後的變化

var ajax = $.ajax({
  url: 'data.json',
  success: function(){
    console.log('success1');
    console.log('success2');
    console.log('success3');
  },
  error: function(){
    console.log('error');
  }
})

console.log(ajax); // 返回一個xhr物件
// 鏈式操作
var ajax = $.ajax('data.json');
ajax.done(function(){
  console.log('success1');
}).fail(function(){
  console.log('error');
}).done(function(){
  console.log()
})

console.log(ajax); // 返回一個deferred物件
  • 無法改變JS非同步和單執行緒的本質
  • 只能從寫法上杜絕callback這種形式
  • 它是一種語法糖形式,但是解耦了程式碼
  • 很好的體現:開放封閉原則(對擴充套件開放,對修改封閉)

使用Jquery Deferred

// 給出一段非常簡單的非同步操作程式碼,使用setTimeout函式
var wait = function(){
  var task = function(){
    console.log('執行完成)
  }
  setTimeout(task, 2000);
}
wait();

新增需求:要在執行完成之後進行某些特別複雜的操作,程式碼可能會很多,而且分好幾個步驟

function waitHandle(){
  var dtd = $.Deferred(); // 建立一個deferred物件
  
  var wait = function(dtd){ // 要求傳入一個deferred物件
    var task = function(){
      console.log('執行完成');
      dtd.resolve(); // 表示非同步任務已經完成
      // dtd.reject(); // 表示非同步任務失敗或出錯
    }
    setTimeout(task, 2000);
    return dtd; // 要求返回deferred物件
  }
  
  // 注意,這裡一定要有返回值
  return wait(dtd);
}

var w = waitHandle();
w.then(function(){
  console.log('ok 1');
}, function(){
  console.log('err 1');
}).then(function(){
  console.log('ok 2');
}, function(){
  console.log('err 2');
})

當執行dtd.reject()時:

var w = waitHandle();
w.then(function(){
  console.log('ok 1');
}, function(){
  console.log('err 1');
})
// 不能鏈式
w.then(function(){
  console.log('ok 2');
}, function(){
  console.log('err 2');
})

上面封裝的waitHandle方法,由於直接返回了dtd(deferred物件),所以使用者可以直接呼叫w.reject()方法,導致無論是成功還是失敗,最後都走失敗。

// 修改
function waitHandle(){
  var dtd = $.Deferred();
  var wait = function(dtd){
    var task = function(){
      console.log('執行完成');
      dtd.resolve(); 
    }
    setTimeout(task, 2000);
    return dtd.promise(); // 注意這裡返回的是promise,而不是直接返回deferred物件
  }
  return wait(dtd);
}
ES6的Promise:點這裡
// promise封裝一個非同步載入圖片的方法
function loadImg(src) {
  var promise = new Promise(function(resolve,reject){
    var img = document.createElement('img');
    img.onload = function(){
      resolve(img)
    }
    img.onerror = function(){
      reject('圖片載入失敗')
    }
    img.src = src;
  })
  return promise;
}

async/await

這是ES7提案中的,現在babel已經開始支援了,koa也是用async/await實現的。
  • then 只是將callback拆分了
  • async/await 是最直接的同步寫法
// 虛擬碼
const load = async function(){
  const result1 = await loadImg(src1);
  console.log(result1);
  const result2 = await loadImg(src2);
  console.log(result2);
}
load();

最後

建立了一個前端學習交流群,感興趣的朋友,一起來嗨呀!