Node.js异步编程,promise,fibers

Node.js异步编程,promise,fibers在 RubyonRails 和 NodeJS 开发者之间曾经引起宗教类的口水战 顺序编程风格 Vs 基于事件编程 目前大部分 Web 应用包括 RubyonRails JavaSpring Django 都是使用顺序编程风格 顺序编程是非常简单和可读的 大部分开发者都是以顺序方式思考 喜欢将一个应用逻辑划分为顺序的时序步骤 顺序编程通常会导致堵塞 I O 因为线程是遵循先来后到的多任务方式 而不是一种

在Ruby on Rails和NodeJS开发者之间曾经引起宗教类的口水战:顺序编程风格 Vs 基于事件编程。目前大部分Web应用包括Ruby on Rails, Java Spring, Django都是使用顺序编程风格。顺序编程是非常简单和可读的,大部分开发者都是以顺序方式思考,喜欢将一个应用逻辑划分为顺序的时序步骤。顺序编程通常会导致堵塞I/O,因为线程是遵循先来后到的多任务方式,而不是一种协作式的多任务方式,而非堵塞I/O能够带来更好地扩展性和性能。

这是一篇有关Node.js的非堵塞reactive编程案例,文章以一个简单的根据id查询的RESTful案例为例,从堵塞IO谈到回调函数的使用,然后谈论如何在代码可扩展性和可读性之间取得平衡,引入Promise与Fibers编程。大意翻译如下:

下面代码是根据facebook的id查询用户事件数据的实现,顺序编程代码风格是如下(JS伪代码):

function getUserEvents(request,response){ var facebook_id = request.param('facebook_id'); try{ var user = db.users.findOne({fb_id:facebook_id}); var events = db.events.find({user_id:user.id}); response.write(events); }catch(err){ response.status(500).send(err); } } 

顺序编程通常会导致堵塞I/O,因为线程是遵循先来后到的多任务方式,而不是一种协作式的多任务方式。

下面我们逐步引入异步来提升这段代码。

基于回调的解决方案
为了解决堵塞的I/O问题,代码可以被划分为三个部分:
1. 在进行网络或IO调用前的过程处理。
2.网络或IO调用。
3.从网络或IO调用中得到返回数据的处理。

上面这三步都是分离执行的,然后它们在NodeJS的事件循环中被触发执行。

function getUserEvents(request,response){ var returnEvents = function(err,events){ if (err) respone.status(500).send(err);; response.write(events); }); var givenUserFindAndReturnEvents = function(err,user){ if (err) respone.status(500).send(err);; db.events.find({user_id:user.id},returnEvents); }; var findUserAndReturnEvents = function(){ var facebook_id = request.param('facebook_id'); db.users.findOne({fb_id:facebook_id}, givenUserFindAndReturnEvents); } findUserAndReturnEvents(); } 


注意到请求和响应并没有被传递到子函数中,但是子函数可以访问request 和response,因为子函数是一个javascript 闭包. (当调用findUserAndReturnEvents函数时,相当于findUserAndReturnEvents -> givenUserFindAndReturnEvents ->returnEvents 这样一个流式调用)

这三个子函数都是异步执行的, givenUserFindAndReturnEvents 和 returnEvents是在findUserAndReturnEvents回调函数中触发执行的。

这三个子函数执行方式可以使用嵌套的lambda函数风格或findUserAndReturnEvents. givenUserFindAndReturnEvents.returnEvents 这种in-line方式。

这段代码有几个特点:
1.代码分离成网络调用前与网络调用后两个阶段
2.子函数调用者为了完成子函数任务必须传递一个回调函数。
3.顺序逻辑被表达成异步了
4.异步代码更加可扩展伸缩,但是也许增加响应延迟。
5.但是回调会引起可读性的问题:回调地狱 callback hell.
6.跟踪执行流程是困难的,因此称为意大利面条代码spaghetti-code.
7.非堵塞的API可能会限制妨碍你组织你的代码,有侵入性。
8.函数是有层次的。

基于Promise的解决方案
为了解决回调地狱,我们使用代码结构库如q promise. Promise库提供一些标准的代码风格和结构,使得代码更加易读。

var loadEventsForUser = function(err,user){ return db.events.find({user_id:user.id}); }; var findUser = function(){ var facebook_id = request.param('facebook_id'); return db.users.findOne({fb_id:facebook_id}); } function getUserEvents(request,response){ var success = function(events){ response.write(events); }; var error = function(err){ response.status(500).send(err); }; findUser() .then(loadEventsForUser) .then(success) .fail(error); } 


关键是最后一行的写法。
代码已经被划分为小的独立的函数,它们被以链条方式链接在一起,使用的是.then 和 .fail 函数. 另外一个重要特性是处理exception, 上面代码能够观察到错误然后调用相应的回调函数,错误处理被单独隔离了。

重要特点:
1.函数是扁平的,比如findUser能独立于loadEventsForUser函数调用。
2.将顺序编程代码切分成独立的可重用的函数并不总是很容易。
3.函数能够使用在其他流程被其他组件复用。
4.相比回调函数有更好的可读性。
5.比顺序编程有着更好地出错处理。

Fibers解决方案
要理解Fibers,需要对抢占式多任务和协作式多任务有一个了解:

抢占式多任务:
在计算中,抢占是暂时中断正在进行的计算任务,而不是与其合作,在其中断以后将继续恢复该任务的执行。这种改变称为上下文切换。
Linux 的调度程序Scheduler (特权任务)会采取取消进程任务,而不是与它的合作。抢占式多任务处理的缺点是操作系统可能会在一个不适当的时间进行上下文切换。

协作式多任务:
早期的多任务处理系统使用的自愿割让时间给另外另一个应用程序。这种方法最终由许多计算机操作系统支持,今天被称为合作多任务处理。
合作多任务依赖于线程一旦在停止是否放弃控制权。合作多任务处理的缺点是如果编写拙劣的代码会堵塞整个系统。实时嵌入式的系统往往采取合作多任务处理范式以获得真正的实时高性能。

Fibers是一个轻量的线程 (也称为绿色线程) ,它是一个进程或应用级别的概念,并不对应着OS的线程,它提供类似执行流的线程,当OS 线程是抢占式调用时,程序员可以使用fibers 实现合作多任务, Fibers概念类似协程coroutines ,执行能够被程序进行暂停或继续。

Fiber代码如下:

var Fiber = require('fibers'); var log_sequence_counter = 1; function sleep(task, milliseconds) { var fiber = Fiber.current; setTimeout(function() { console.log(log_sequence_counter++ + task + ' callback'); fiber.run(); }, milliseconds); console.log(log_sequence_counter++ + task + ' thread/fiber suspended'); Fiber.yield(); console.log(log_sequence_counter++ + task + ' thread/fiber resumed'); } var task1 = function() { console.log(log_sequence_counter++ + ' task 1 waiting for sleep to end '); sleep(" task 1",1000); console.log(log_sequence_counter++ + ' task 1 got back from sleep'); } var task2 = function() { console.log(log_sequence_counter++ + ' task 2 waiting for sleep to end '); sleep(" task 2", 1000); console.log(log_sequence_counter++ + ' task 2 got back from sleep'); } Fiber(task1).run(); Fiber(task2).run(); console.log( log_sequence_counter++ + ' main execution flow'); 


有如下特点:
1. Fibers 是使用Fiber() 函数创建,任务功能是使用run方法执行。
2. 一个Fiber可以暂停或继续. 这种方式可以在进行非堵塞IO调用时反复使用。
3. 当一个fiber线程Fiber.current调用时,返回是当前正在执行的fiber.
4. Fiber.yield是暂停当前线程执行,这样允许其他fiber线程能够协作地执行。
5. task1 和 task2 函数并没有回调函数或promises,它是顺序编程代码
6. task1 和 task2 函数并不知道fibers,这样开发者能够作为普通顺序代码读写改这些代码函数。
7.Fibers提供协作多任务能力。

上述代码输出结果:

$ node fibersExample.js 1 task 1 waiting for sleep to end 2 task 1 thread/fiber suspended 3 task 2 waiting for sleep to end 4 task 2 thread/fiber suspended 5 main execution flow 6 task 1 callback 7 task 1 thread/fiber resumed 8 task 1 got back from sleep 9 task 2 callback 10 task 2 thread/fiber resumed 11 task 2 got back from sleep 


上述输出结果编号显示执行顺序,即使Node.js是一个单线程,它也能够以非堵塞方式执行多任务。

原文:http://www.jdon.com/46372

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/216194.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午12:20
下一篇 2026年3月18日 下午12:20


相关推荐

  • 关于2019高压油管网格搜索可视化Python作图

    关于2019高压油管网格搜索可视化Python作图网格搜索网格搜索 通过在一张网内对 x yx yx y 离散搜索 可以得出关于目标函数在每一个 x yx yx y 散点的值 做可视化的时候我想到了山峰的三维表面图 一个简单的事例已知 xxx 的范围 yyy 的范围 还有对应每一个 x yx yx y 的高程数据 zzz 我们就可以利用 python 作图 x np array 0 1 2 3 4 y np array 0 1 2 3 z np array 122 424 221 231 742 231 421

    2026年3月26日
    2
  • ostaskcreate函数作用_认识上中下

    ostaskcreate函数作用_认识上中下OSTaskCreate()是学习ucos-Ⅱ操作系统的第一个函数,费了九牛二虎之力,现在感觉差不多可以过了。#ifOS_TASK_CREATE_EN>0INT8UOSTaskCreate(void(*task)(void*p_arg),void*p_arg,OS_STK*ptos,INT8Uprio)/*1*/{ OS_STK…

    2025年9月17日
    7
  • windows10虚拟机安装_虚拟机一键安装版

    windows10虚拟机安装_虚拟机一键安装版[XP虚拟机安装]VMware安装XP虚拟机下载VMware下载XPProfessional,我是在这个网站下的http://www.runker.net/windows-xp-sp3.html新建虚拟机,选择典型选择刚刚下载的XP镜像文件(这里用简易安装更方便,如果不用简易安装还需要进BIOS设置CD启动项啥的,安装过程也需要自己一步步操作,麻烦一些)点击下一步,要求填写秘钥,百度搜索秘钥填进去可以参考https://zhidao.baidu.com/question/45439092

    2022年8月16日
    8
  • LBD算法 – Graph matching 图匹配算法分析

    LBD算法 – Graph matching 图匹配算法分析上一步我们进行了线特征的提取和描述 接下来我们进行线特征的匹配 在此之前我们先通过预处理将一些明显无法匹配的特征给消除 以降低图匹配问题的维度

    2026年3月16日
    2
  • 详细讲解mysql 主从复制原理「建议收藏」

    详细讲解mysql 主从复制原理

    2022年2月19日
    52
  • IIC通信协议总结(详细说明完整过程)

    IIC通信协议总结(详细说明完整过程)IIC协议简介IIC(inter-integratedCircuit集成电路总线)总线支持设备之间的短距离通信,用于处理器和一些外围设备之间的接口,它需要两根信号线来完成信息交换。IIC的一个特殊工艺优势是微控制器只需要两个通用I/O引脚和软件即可控制芯片网络。IIC最早是飞利浦在1982年开发设计并用于自己的芯片上,一开始只允许100Khz、7-bit标准地址,1992年,IIC的第一个公共规范发行,增加了400Khz的快速模式以及10bit地址扩展。IIC协议IIC协议把传输的消息分为两种类型

    2022年5月2日
    146

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号