资讯专栏INFORMATION COLUMN

JavaScript异步操作(续)

Travis / 543人阅读

摘要:环境中产生异步操作的函数分为两大类计时函数和函数。如果要在应用中定义复杂的异步操作,就要使用者两类异步函数作为基本的构造快。在本例子中同步事件循环不包含内部的外部的异步事件循环内部的。其弊端是操作强耦合维护代价高。

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

yieldyield*的区别:

yield只返回右值;

yield*将函数委托给另一个生成器,或可迭代的对象(字符串、数组、arguments、Map、Set)

注: yield关键字只能出现在生成器函数里面!!!否则会报错:Unexpected strict mode reserved word

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://www.ucloud.cn/yun/82601.html

相关文章

  • 编写自己的代码库(javascript常用实例的实现与封装--

    摘要:前言这个系列的上一篇文章编写自己的代码库常用实例的实现与封装总结了个常见的操作。前序修改以及写法优化此处修改之前提交函数已经发现的,基于这个系列上篇文章的提供的函数。 1.前言 这个系列的上一篇文章(编写自己的代码库(javascript常用实例的实现与封装))总结了34个常见的操作。但是在开发中,常见的实例又何止这么多个,经过这些日子的探索,以及他人的意见,现在得追加一些操作实例了。...

    Atom 评论0 收藏0
  • ES6 异步编程之三:Generator

    摘要:前言在异步编程之一中实现了一个异步函数调用链,它是一个顺序调用链,很类似责任链模式,但现实往往不是平铺直叙的,更多的其实是峰回路转,本文将继续讨论更多的用法。 前言 在《ES6 异步编程之一:Generator》中实现了一个异步函数调用链,它是一个顺序调用链,很类似责任链模式,但现实往往不是平铺直叙的,更多的其实是峰回路转,本文将继续讨论更多Generator的用法。 作为函数的Gen...

    JiaXinYi 评论0 收藏0
  • 次时代Java编程(一): vertx-sync实践

    摘要:定时器例子之前通过调用定时器,需要传一个回调,然后所有的代码逻辑都包在里面。这里定时器会阻塞在这一行,直到一秒后才会执行下面的一行。 之前介绍过quasar,如果你希望在vert.x项目里使用coroutine的话,建议使用vertx-sync。本篇将介绍vertx-sync。 showImg(/img/bVzIsu); 本来打算另起一篇,写其他方面的东西,但是最近比较忙,就先写一篇实...

    vpants 评论0 收藏0
  • 为什么 Math.min() 比 Math.max() 大?(

    摘要:本来以为是无参调用时返回了运算的幺元,后来细琢磨,好像没有什么关系,对于运算集合上的二元运算,如果满足,则是运算的幺元。乘法运算的幺元是,因为。但是我们定义函数或者函数,如果不传递参数时,返回幺元的话也是不合理的。 昨天心血来潮写了一篇文章:为什么Math.min() 比 Math.max() 大? 为什么很多人会有这种疑惑,是因为犯了想当然的错误——望文生义。Math.min() 作...

    xiguadada 评论0 收藏0

发表评论

0条评论

Travis

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<