浅谈异步编程

线程与进程

众所周知,Javascript的执行环境是'单线程'。线程的定义又是什么呢?在说到线程之前我们先了解下进程。

进程:进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。

线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

一个程序运行后至少有一个进程,一个进程可以包含多个线程。

多线程对于高并发量就有益,但多线程程序会出现多个线程对一个资源进行访问的问题。具体的解决方案是利用锁。单线程就不会出现此类的问题。但单线程如果出现堵塞,就会而独占cpu导致其他代码不能运行。其解决方案就是异步编程。node是单线程,Java是多线程。

nodejs里面的异步。

nodejs里面大部分的api都是异步,少量的api是同步的。I/O操作会比较耗时但不会独占CPU,典型的I/O比如文件读写,远程数据库读写,网络请求等.耗时的解决方案就是异步。在node.js进程里面,有一个用户线程(javascript所宣称的单线程)和一个异步线程池(用户无法直接访问), 如果跑在异步线程上的代码是阻塞的,那么这种异步根本就起不到消除阻塞的作用.原因就是阻塞代码会霸占cpu,导致本进程所有代码都等待不管是哪个线程。但是node.js里面的I/O API都是不会霸占CPU的,所以是非阻塞的,就不会出现这个问题。这就是node.js的最引以为傲的特性之一:异步非阻塞I/O.

javascript异步编程的方式

  • 回调函数。

回调函数用来定义一次性响应的逻辑。比如说对于数据库查询,可以指定一个回调函数来处理如何处理查询结果。

function f2(){
  console.log("f1任务代码")
}
function f1(callback){

        setTimeout(function () {

        // f1的任务代码

            callback();

        }, 1000);
}

f1(f2);
复制代码

这里的异步api是浏览器提供的setTimeout,回调函数callback会被放入事件队列里面,不会阻塞cpu,也就是说代码会继续往下执行.等setTimeOut完成后,再执行callback。

var http = require("http");
var fs = require("fs");

http.createServer(function (req, res) {
    if(req.url == '/'){
        fs.readFile('./title.json',function (err, data) {
            if(err){
                console.log(err);
                res.end('Server error');
            }else {
                var titles = JSON.parse(data.toString());
                
                fs.readFile('./template.html',function (err, data) {
                    if(err){
                        console.log(err);
                        res.end('Server error');
                    }else {
                        var templ = data.toString();
                        var html = templ.replace('%', titles.join('<li></li>'));
                        res.writeHead(200, {'Content-Type': 'text/html'});
                        res.end(html);
                    }
                })
            }
        })
    }
}).listen(8000, "127.0.0.1");
复制代码

上面的例子就是node的一些I/O操作。回调函数层层嵌套,这也就是传说中的'callback hell'.写起来很爽,简单、容易理解.但维护的人估计得疯。解决方法是创建中间函数以减少嵌套,或者是node中惯用手法减少if/else引起的嵌套:尽早在函数中返回。详情请见node in action一书中3-2;

  • 事件监听。

本质也是一个回调,但它和一个概念实体(事件)有关联。点击鼠标是一个事件。当然在服务器端,当有HTTP请求过来的时候,HTTP服务器会发出一个请求事件。你所要做的就是监听那个请求事件,并添加一些响应逻辑。

谈到事件,我们就必须要说到观察者模式(observer pattern)。这种模式在编程届无处不在。阮一峰给出的定义是:

我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern),又称"观察者模式"(observer pattern)。

我一直坚信程序是生活的抽象。不要以为你每天面对的是一个机器,其实它也是一个世界。坐卧铺车,列车员收走车票,我们订阅这个事件,就相当于订阅者,列车员相当于观察者。等车到了站,发布这个事件,列车员就会来叫我们。我们执行下车这个回调。

这种模式有多种的实现,而他的应用就不胜枚举了,比如rxjs里面的响应式数据,redux里面的subscribe等等.

node里面的http服务器实例就是一个事件发射器,也是这种模式的应用。

该实例来自于node in action.

var events = require("events");
var net = require("net");

var channel = new events.EventEmitter();

channel.clients = {};

channel.subscriptions = {};

channel.on('join',function (id, client) {
    this.clients[id] = client;
    this.subscriptions[id] = function (senderId, message) {
        if(id != senderId){
            this.clients[id].write(message);
        }
    };
    var welcome = "Welcome!\n" + "Guests online " + this.listeners('broadcast').length;
    client.write(welcome + '\n');
    this.on('broadcast', this.subscriptions[id]);
});

//当有用户离开时,通知其他用户;
channel.on('leave',function (id) {
   channel.removeListener('broadcast',this.subscriptions[id]);
    channel.emit('broadcast',id, id + "has left the chat.\n");
});

var server = net.createServer(function (client) {
    var id = client.remoteAddress + ': ' + client.remotePort;
    // client.on('connect', function () {
        channel.emit('join', id, client);
    // });
    client.on('data', function (data) {
        var data = data.toString();
        channel.emit('broadcast', id, data);
    });
    client.on('close',function () {
        channel.emit('leave',id);
    })
});

server.listen(8888);
复制代码

这里channel对象继承自EventEmitter,利用on订阅事件,emit发布事件。执行相应的回调逻辑。EventEmitter核心是事件触发与事件监听器功能的封装,大多数模块多是继承它。fs,net,http.

  • Promise对象。

Promises对象是CommonJS工作组提出的一种规范,目的是为异步编程提供统一接口。简单说,它的思想是,每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。 这里简单介绍下Promise.想要就具体介绍可以了解下你不知道的JavaScript 中和阮一峰的es6入门

Promise只是改善了回调函数的写法,使回调函数变成了链式写法。并且可以执行多次的回调。废话不多说,直接上代码。


const promise = new Promise(function(resolve, reject) {
  // ... 这里可以做一些异步的操作;

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

复制代码
  • Generator函数.

执行Generator函数后会返回一个遍历器对象(Iterator)。本质上Generator是一个状态机,每次的遍历Generator函数返回的都是函数内部的每个状态。

function* gen(){
    yield 1;
    yield 2;
    return 1;
}

var g = gen();

gen.next() // {value:1,done:false}

gen.next() // {value:2,done:false}

gen.next() // {value:3,done:true}

复制代码

调用Generator函数,返回遍历器对象,代表Generator函数内部指针。每次调用next方法,就会返回一个value和done属性的对象,value是yield后面表达式的值。done表示便利是否结束。

Generator函数的异步编程;

var fetch = require('node-fetch');

function* gen(){
  var url = 'https://api.github.com/users/github';
  var result = yield fetch(url);
  console.log(result.bio);
}

var g = gen();
var result = g.next();

result.value.then(function(data){
  return data.json();
}).then(function(data){
  g.next(data);
});

复制代码

这里的fetch请求数据。相当于ajax. result.value是fetch(url)返回的值是一个Promise. 所以可以用then语法。这里注意下yield语句本身没有返回值。在next中参数将作为上一个yield的返回值。

  • async和await async函数是Generator函数的语法糖。
const fs = require('fs');

const readFile = function (fileName) {
  return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function(error, data) {
      if (error) return reject(error);
      resolve(data);
    });
  });
};

// Generator函数版本

const gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

// async函数版本。
const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

复制代码

我们可以发现sync函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。 但有一些区别async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。

进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。

转载于:https://juejin.im/post/5b11eb966fb9a01e85641743

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值