JavaScript異步操作

lgrcyanny 8年前發布 | 13K 次閱讀 Koa.js JavaScript開發

JavaScript環境中產生異步操作的函數分為兩大類: 計時函數 和 I/O函數 。如果要在應用中定義復雜的異步操作,就要使用者兩類異步函數作為基本的構造快。本文沒有對某個知識點細致展開,僅供思路參考。

1. 計時函數

先看一個經典的例子:

for(var i = 0;i < 5; i++){
    setTimeout(function(){
        console.log(i);
    },5000);
}
console.log(i);

結果輸出什么?立馬輸出一個5,5秒鐘過后連續輸出5個5(嗚嗚嗚嗚嗚~),搞懂為什么這樣輸出5,需要知道3件事:

  • 這里只有一個 i 變量,作用域由 var 定義,不管i怎么變,都指向同一個內存區域;

  • 循環結束后 i=== 5;

  • JavaScript事件處理器在線程空閑之前不會執行。

來,再來和我背一遍口訣: 先同步后異步最后回調 。在本例子中:

  • 同步事件:for循環(不包含內部的setTimeout);外部的console.log(i);

  • 異步事件:for循環內部的setTimeout。

先執行for循環,遇到setTimeout壓入延遲事件隊列,一邊循環一邊壓入隊列;for循環結束執行外部的console.log(i),此時i=5,故立即輸出5,此時同步事件執行完畢,接下來開始執行異步事件setTimeout,5個setTimeout事件等待5秒同時在等待,所以5秒結束連續輸出5個5。

再看一個例子:

var start = new Date;
setTimeout(function(){
    var end = new Date;
    console.log('time using:' + (end - start) + 'ms');
},5000);
while(new Date - start < 1000){};

猜猜結果是什么?

調換一下時間:

var start = new Date;
setTimeout(function(){
    var end = new Date;
    console.log('time using:' + (end - start) + 'ms');
},2000);
while(new Date - start < 5000){};

這里唯一想說的是:像 setTimeout 或是 setInterval 并非是精確計時的, setTimeout與setInterval在不同瀏覽器下的差異

2. I/O函數

這里的 I/O 是一個廣義的概念,包括讀寫文件,GET或POST請求,異步讀取值值函數等等。一個常見的讀文件的操作:fs.js

var fs = require('fs');

fs.readFile('data.txt',function(err,data){
    if(err){
        return console.log(err);
    }
    console.log(data.toString())
});

data.txt的內容:

hello
world
hello
async

node fs.js:

沒毛病!設想一個場景,在另外一個函數,假設名字叫 panfen() ,里面有一堆代碼,其中需要用到從data.txt文件讀取的數據,常見的做法是把fs.readFile操作寫在 panfen() 里面,處理data的操作都寫在fs.readFile的callback里面,如果callback里面還要寫數據呢?繼續執行fs.writeFile在它的callback里面執行其他操作...

var fs = require('fs');

fs.readFile('data.txt',function(err,data){
    if(err){
        return console.log('failed to read data!');
    }else{
        fs.writeFile('./data_copy.txt', data, function(){
            if(err){
                ...
            }else{
                ...
            }
        });
    }
});

這就是 回調金字塔 。其弊端是操作強耦合、維護代價高。如何做到理想的異步呢?

靈機一動這樣寫:

var fs = require('fs');

var data = fs.readFile('data.txt',function(err,data){
    return data ? data : err;
});
fs.writeFile('./data_copy.txt', data, function(){
    ...
});

然而,根據 先同步后異步最后回調 的法則,fs.writeFile里面使用到的data,肯定是 undefined

3.Promise

var fs = require('fs')

function myReadFile(filepath) {
    return new Promise(function (resolve, reject) {
        fs.readFile(filepath, function (err, data) {
            data ? resolve(data) : reject(err);
        });
    });
}

function myWriteFile(filepath,data) {
    return new Promise(function (resolve, reject) {
        fs.writeFile(filepath,data,function (err) {
            err ? reject(err) : resolve();
        });
    });
}

function test() {
    myReadFile('data.txt').then(function(data){
        myWriteFile('./data1.txt',data)
    });
}
test();

Promise的特點在于可以用then方法向下傳遞值。

4. Generator

4.1 function*

關于生成器函數,這里不作具體介紹,簡單看一例子:

function* GenFunc(){
    yield [1,2];
    yield* [3,4];
    yield "56";
    yield* "78"  
}
var gen = GenFunc();
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());

yield 和 yield* 的區別:

  • yield 只返回右值;

  • yield* 將函數委托給另一個生成器,或可迭代的對象(字符串、數組、arguments、Map、Set)

4.2 co

co是基于Generator的一個庫:

var fs = require('fs');
var co = require('co');

function myReadFile(filepath){
    return function(cb){
        fs.readFile(filepath,cb);
    };
}

function myWriteFile(filepath,data){
    return function(cb){
        fs.writeFile(filepath,data,cb);
    };
}

co(function* GenFunc(){
    var data = yield myReadFile('data.txt');
    yield myWriteFile('data1.txt',data);
}).catch(function(err){
    console.log(err);
});

看起來是不是神清氣爽?

4.3 Koa

Koa是基于Generator和co的web框架。

var koa = require('koa');
var app = koa();

app.use(function* (next){
    console.log(1);
    yield next;
    console.log(3);
});
app.use(function* (){
    console.log(2);
    this.body = 'hello Koa!';
});
app.listen(8080);

啟動程序,流浪器輸入 localhost:8080 ,看到頁面有 hello Koa! ,控制臺輸入 1 2 3

5.async/awit

隨著 Node 7 的發布,越來越多的人開始研究async/await,據說這是異步編程終級解決方案的 。個人覺得沒有最好,只有更好。用這種方式改寫:

'use strict'
var fs = require('fs')

function myReadFile(filepath) {
    return new Promise(function (resolve, reject) {
        fs.readFile(filepath, function (err, data) {
            data ? resolve(data) : reject(err);
        });
    });
}

function myWriteFile(filepath,data) {
    return new Promise(function (resolve, reject) {
        fs.writeFile(filepath,data,function (err) {
            err ? reject(err) : resolve();
        });
    });
}

async function test() {
    const data = await myReadFile('data.txt');
    await myWriteFile('./data1.txt',data);
}
test();

這個例子還不足以體現async/awit的優勢。

6. 總結

JavaScript的異步操作可能是區別其他語言比較大的一點,也是一個難點,不過也是很有趣的嘛。

 

來自:https://segmentfault.com/a/1190000009131570

 

 本文由用戶 lgrcyanny 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!