javascript異步編程學習及實例

所謂異步就是指給定了一串函數調用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

使用XMLHttpRequest實現promise形式的ajax

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);
})

3. ES7 async/await關鍵字

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中獲取

相關文章
相關標籤/搜索