所謂異步就是指給定了一串函數調用a,b,c,d,各個函數的執行完結返回過程並不順序爲a->b->c->d(這屬於傳統的同步編程模式),a,b,c,d調用返回的時機並不肯定。javascript
對於js代碼來講,這種異步編程尤爲重要並大量存在,很大的緣由是js爲單線程模式,其中包含了ui刷新和響應。想像一下,若是咱們寫了一段js代碼,他會發起ajax請求,若是爲同步模式,就意味着這時js單線程一直等待這個ajax完成,這期間是不能響應任何用戶交互的,這種體驗是不可接受的。如今隨着服務端js-nodejs的興起,其超強的異步模式更爲nodejs相較於php,java等成熟後端語言的賣點。全部和IO相關的都應該設計成異步模式(好比磁盤IO,網絡請求等)php
實現異步編程模式能夠有如下方式:java
1. 回調callback,node
看如下nodejs代碼:須要注意的是,即便在服務端的nodejs環境中,其運行模型也是單線程模型!http://www.javashuo.com/article/p-kjgpbgsk-bk.htmlc++
// 遍歷目錄,找出最大的一個文件 // nodejs的readdir爲一個典型的異步調用過程,函數調用立刻返回,可是結果卻等到目錄掃描完成後,調用回調函數來通知應用去處理 const fs = require('fs'); const path = require('path'); function findLargest(dir, callback) { fs.readdir(dir, function (err, files) { if (err) return callback(err); // [1] let count = files.length; // [2] let errored = false; let stats = []; files.forEach( file => { fs.stat(path.join(dir, file), (err, stat) => { if (errored) return; // [1] if (err) { errored = true; return callback(err); } stats.push(stat); // [2] if (--count === 0) { let largest = stats .filter(function (stat) { return stat.isFile(); }) .reduce(function (prev, next) { if (prev.size > next.size) return prev; return next; }); callback(null, files[stats.indexOf(largest)]); console.log('readdir finished!') } }); }); }); console.log('before readdir callback called!') } findLargest('./', function (err, filename) { if (err) return console.error(err); console.log('largest file was:', filename); }); // 其執行log以下 before readdir callback called! largest file was: halls-test.js readdir finished!
咱們看看經過node --inspect-brk 調試的過程:web
$ node --inspect-brk maxfile.js Debugger listening on ws://127.0.0.1:9229/ed354fe8-fdfc-466b-b0ea-fcc7fccb4b36 For help, see: https://nodejs.org/en/docs/inspector Debugger attached. before readdir callback called! largest file was: halls-test.js readdir finished! Waiting for the debugger to disconnect...
callback致使的問題是沒法經過try catch截取錯誤,而且當回調嵌套時流程更加顯得複雜,程序可讀性差;callback將在fs.readdir的function參數中調用,該callback(本例中其實是fs.readdir的function參數)將被fs.readdir調用操做真正異步執行完成時(自己函數調用當即返回,而執行經過系統調用異步執行),放入javascript event queue中,底層readdir實際操做(每每是由js引擎c++代碼執行)結束後,將有event發生,這時會將該callback function放到event queue中(幷包含了對應的readdir返回數據),從而由event loop引擎在js主線程的運行週期的適當時機來調用ajax
2. promise編程
promise表明了一個異步操做最終的結果,它是一個對象,表明着延遲計算(deferred computation)的最終結果(除了延遲計算,更多的是一個異步的IO操做). promise也是一種狀態機,它有三個不一樣的狀態:pending, fulfilled,rejected.一旦promise完成(fulfilled,或者rejected),它就不能再被變動狀態。json
when a promise is ready, its .then/catch/finally
handlers are put into the queuec#
當promise resolve/reject時,也就是該promise ready時,會將promise的then定義的handler插入event queue,在下一個event loop週期時,若是主線程沒有任務執行了,將被取出執行
再看看如下代碼對應的解讀:
let promise = Promise.resolve(); promise.then(() => alert("promise done")); alert("code finished"); // this alert shows first
若是咱們但願promise done的打印在code finished打印以前,怎麼辦?答案是then的連接,每個then都會返回一個新的promise
Promise.resolve() .then(() => alert("promise done!")) .then(() => alert("code finished"))
promise 的實現機制: http://www.javashuo.com/article/p-xzyfgegj-ma.html
Promise.resolve schedule a microtask and the setTimeout schedule a macrotask. And the microtasks are executed before running the next macrotask
function get(url) { // Return a new promise. return new Promise(function(resolve, reject) { // Do the usual XHR stuff var req = new XMLHttpRequest(); req.open('GET', url); req.onload = function() { // 這裏是原生的callback api,也就是當onload事件發生時會被event loop調用,從而經過resolve再push到event queue中,對應then中的handler被下一個loop調用 // This is called even on 404 etc // so check the status if (req.status == 200) { // Resolve the promise with the response text resolve(req.response); } else { // Otherwise reject with the status text // which will hopefully be a meaningful error reject(Error(req.statusText)); } }; // Handle network errors req.onerror = function() { reject(Error("Network Error")); }; // Make the request req.send(); }); }
https://developers.google.com/web/fundamentals/primers/promises?hl=zh-tw
get('story.json').then(function(response) { console.log("Success!", response); }, function(error) { console.error("Failed!", error); })
var sleep = function (time) { return new Promise(function (resolve, reject) { setTimeout(function () { // 返回 ‘ok’ resolve('ok'); }, time); }) }; var sleepwithReject = function (time) { return new Promise(function (resolve, reject) { setTimeout(function () { // 返回 ‘ok’ reject('Error'); }, time); }) }; var start = async function () { let result = await sleep(3000); console.log(result); // 收到 ‘ok’ }; var reject = async function () { try{ let result = await sleepwithReject(3000); console.log(result); // 不會執行,由於被reject了,會觸發一個異常 }catch (err){ console.log(err);// 這裏捕捉到Error } } ; start() reject()
基本規則:
1. async關鍵字表示這是一個async函數,await關鍵字只能在這個async關鍵字指示的函數中;
2. await表示在這裏等待promise執行完成並返回結果,promise完成後才能繼續執行
3. await後面跟着的應該是一個promise對象(固然若是是非promise對象,則只會當即執行)
4. await緊跟的promise resolve/reject以後其resolve或者reject返回的結果直接在這裏能夠synchrosely(同步地)返回
5. 若是發生錯誤,則能夠在try catch中獲取