Quantcast
Channel: CNode:Node.js专业中文社区
Viewing all 14821 articles
Browse latest View live

一只搬运文章的小毛贼

$
0
0

不要问我为啥要写这样的代码,嘿嘿嘿~~~~~

1.文章自动上传到steemit

本来想分析网站的api进行上传的,没想到steemit做了众多限制,感觉有些麻烦所以采用直接控制浏览器的方法上传文章。 node.js使用selenium-webdriver模块可以轻松控制浏览器。

/**
   * 登录
   */
  async login() {
    let url = `https://steemit.com/login.html`;
    let _this = this;
    let returnJson = {
      result: false,
      value: ""
    };
    return new Promise(async (resolve, reject) => {
      await _this.web.get(url);
      await _this.web.findElements(By.css("input")).then(async res => {
        await res[1].sendKeys(_this.__username);//设置登录用户名
        await res[2].sendKeys(_this.__password);//设置登录密码
        await _this.web
          .findElement(By.css(".login-modal-buttons>button"))
          .click();
        await _this.web.wait(until.urlIs("https://steemit.com/welcome"), 4000);
        await _this.web
          .findElement(By.css(".show-for-medium.submit-story"))
          .click();
        returnJson.result = true;
        returnJson.value = "login success";
        console.log("login success");
        resolve(returnJson);
        return returnJson;
      });
    });
  }

  /**
   * 提交文章
   * @param {*} title 文章标题
   * @param {*} post 文章体
   * @param {*} tags 文章标签
   */
  async submit(title, post, tags) {
    let url = `https://steemit.com/submit.html`;
    let _this = this;
    let returnJson = {
      result: false,
      value: ""
    };
    return new Promise(async (resolve, reject) => {
      await _this.web.get(url);
      //设定延时,确保进入提交页面
      await _this.web.wait(
        until.urlIs("https://steemit.com/submit.html"),
        4000
      );
      //标签定位,打开网页分析一下就oK了
      await _this.web.findElements(By.css("input")).then(async res => {
        await res[1].sendKeys(title);
        await res[3].sendKeys(tags);
        await _this.web.wait(until.elementLocated(By.css("textarea")), 3000);
        await _this.web.findElement(By.css("textarea")).sendKeys(post);
        await _this.web.findElement(By.css(".button")).click();
        returnJson.result = true;
        returnJson.value = "submit success";
        resolve(returnJson);
        return returnJson;
      });
    });
  }

标签定位大家只用打开浏览器看一下就知道了 先调用登录接口,登录成功后调用上传文章接口

2.文章来源-从medium批量下载文章到redis队列

根据一位道友提供的api接口可以根据作者名获取到该作者的文章列表。

/**
   * 获取Medium文章列表
   * @param {*} user
   * @param {*} limit
   */
  async getPostByUsername(username, limit) {
    let returnJson = {
      result: false,
      value: ""
    };
    let _this = this;
    return new Promise((resolve, reject) => {
      if (
        typeof limit != "number" ||
        limit <= 0 ||
        limit > 100 ||
        typeof username != "string" ||
        username === ""
      ) {
        returnJson.result = false;
        returnJson.value = "参数不正确";
        resolve(returnJson);
        return returnJson;
      } else {
        let JSONDate = {
          query: `query PostQuery($username: String!, $limit: Int!){
                posts(username: $username, limit: $limit) {
                  title
                  firstPublishedAt
                  url
                  content {
                    subtitle
                  }
                }
                user(username: $username) {
                  username
                  name
                  bio
                }
              }`,
          variables: `{
                "username": "${username}",
                "limit": ${limit}
              }`,
          operationName: "PostQuery"
        };

        superagent
          .post("https://micro-medium-api.now.sh/graphql")
          .send(JSONDate) // sends a JSON post body
          .set("accept", "json")
          .end((err, res) => {
            if (err) {
              console.log("获取文章列表出错: ", err);
              returnJson.result = false;
              returnJson.value = "获取文章列表出错: " + err;
              resolve(returnJson);
              return returnJson;
            } else {
              returnJson.result = true;
              returnJson.value = res.text;
              resolve(returnJson);
              return returnJson;
            }
          });
      }
    });
  }

又是一个神器h2m,(根据url将html转化成md)

npm install h2m -g
h2m https://baidu.com

于是我有封装了一个接口

/**
   * 根据url下载Medium文章到本地
   * @param {*} url
   * @param {*} savePath
   * @param {*} saveName
   */
  async downloadPostByUrl(url, saveName,author,category) {
    let returnJson = {
      result: false,
      value: ""
    };
    let _this = this;
    url = encodeURI(url);
    return new Promise((resolve, reject) => {
      if (url === "" || saveName === "" ) {
        returnJson.result = false;
        returnJson.value = "参数不正确";
        resolve(returnJson);
        return returnJson;
      } else {
        let command = `h2m ${url}`;
        console.log(command);
        exec(command, {timeout: 1000*60*3,maxBuffer: 20*1024*1024},(error, stdout, stderr) => {
          console.log(`stderr: ${stderr}`);
          if (error) {
            console.log(`exec error: ${error}`);
            returnJson.result = false;
            returnJson.value = `exec error: ${error}`;
            resolve(returnJson);
          } else {
            returnJson.result = true;
            returnJson.value = `根据url下载Medium文章到本地成功`;
            console.log('根据url下载Medium文章到本地成功');
            resolve(returnJson);
            let saveStr = stdout.slice(stdout.indexOf('\n\n---\n'),stdout.indexOf('One clap, two clap, three clap, forty?'))
            let title = saveName;
            let postJson = {
              title: title,
              author: author,
              category: category,
              content: saveStr
            }
			//存储到redis队列中
            _this.queueJson[category].publish(postJson)
          }
          return returnJson;
        });
      }
    });
  }

小坑:exec使用时stdout, stderr默认大小是200K,要把maxBuffer设置大一点才行。

3.如何保证上传的文章不重复呢?

真心感谢无所不能的npm 使用redis-message-queue可以轻松创建出值唯一的redis队列。

this.client = new rmq.UniqueQueue(this.name, port, host);

4.运行

代码位置:https://gitee.com/null_639_7345/steemit 1.git代码到本地 2.下载firefox驱动文件到本地,安装firefox浏览器 3.npm i h2m -g 4.修改配置文件config/default.js

module.exports = {
    cwd : 'F:\\test1\\steemit\\',//项目根目录
    redisHost: '192.168.10.6',//redis服务器ip
    redisPost: 6379,
    promulgatorName: '***',//steemit用户名
    promulgatorPassword: "***",//steemit密码
    categories:[
        {
            name: 'popular',//steemit上传文章的分类(tag)
            //Medium的作者列表
            origin: ['joshrose','JessicaLexicus','ThunderPuff','usemuzli','black_metallic']
        }
    ]
};

5.node index.js

最后可以告诉大家一个激动的好消息。 image.png


[北京][腾讯]招聘高级前端工程师(t3)

$
0
0

工作职责:

负责腾讯新闻后台CMS系统的相关开发工作,为用户提供更好的用户体验和更稳定的系统。参与腾讯新闻插件,腾讯新闻内容管理系统,腾讯新闻小程序等项目的开发。

职级: t3

工作要求:

  • 正规大学全日制本科及以上学历,工作至少四年以上。
  • 熟悉最新Web前端框架(React angular Vue),并精通其中至少一种。
  • 对web标准化有深入见解,良好的编码风格以及抽象思维。
  • 有移动端web开发经验,或者熟悉nodejs编程。
  • 有开源项目者优先。

如有兴趣请发简历:ajiemath@gmail.com

express搭建的淘宝客网站

$
0
0

express搭建的淘宝客网站

测试地址: www.5aiss.com

项目地址:https://gitee.com/null_639_7345/taobaoke

效果

1.png2.png3.png4.png

1.运行

  • 安装mongodb数据库
  • 修改配置文件config/default.js
  • 配置淘宝api信息 \taobaoke\server\tbk\TbkAPI.js
var client = new TopClient({
  appkey: "***",//你的appkey
  appsecret: "***",//appsecret
  REST_URL: "http://gw.api.taobao.com/router/rest"
});

let api = (module.exports = {
  adzone_id: "***",//你的adzone_id
  fields:'num_iid,title,pict_url,small_images,reserve_price,zk_final_price,user_type,provcity,item_url'
});

不清楚的请参考-book/淘宝客API使用方法.doc

  • 安装依赖库
npm i gulp -g
npm i
  • 前端打包
gulp
  • 启动web服务
node ./bin/www

[北京] 京东 高级前端工程师 3名

$
0
0

工作内容:

  1. 负责前端界面的前端构建,各类交互设计与实现
  2. 负责前端页面架构设计、组件优化、技术创新工作
  3. 参与产品需求的分析,与设计师、后端工程师一起完成产品功能的开发,优化用户体验 任职要求:
  4. 有熟练的移动端经验,熟练掌握HTML5代码实现、调优。
  5. 熟悉HTML5、CSS3等技术,有移动Web开发经验。
  6. 精通原生Javascript,熟练使用ES6,熟悉 jQuery、Bootstrap、Node.js 等,React、Vue等主流 MVVM 框架掌握至少之一,能独立开发高质量组件,能快速高效实现各种交互效果。
  7. 能够很好的与产品、设计师和后台开发人员沟通协作,有良好的沟通能力、协调能力、强烈的责任心和团队合作精神 5:能够解决跨平台(微信、Android 、IOS、 PC)页面的适配和兼容性问题
  8. 熟练使用webpack 、gulp等前端自动构建工具,了解前端项目模块化解决方案。
  9. 熟悉至少一种后台编程语言(PHP/Python等)者优先

加分项 有 ReactNative or Weex 开发经验

待遇:15-30k 工作地点:北京-朝阳区 经验和学历: 3-5年 本科或以上 简历要求: 姓名_高级前端工程师.pdf 简历邮箱:huotianliang@jd.com

[北京] 滴滴出行招聘前端实习生

$
0
0

[北京] 滴滴出行招聘前端实习生

实习岗位名称:

前端研发实习生

招募公司:

滴滴

所属部门组:

智慧交通

工作地点:

尚东数字山谷

交通情况

西二旗地铁站有班车

实习时间(包括实习期的长度,每日实习的时间):

每周工作至少4天,实习期四个月以上

福利情况(包括是否包吃住等):

水果,日常供应零食,加班工作餐,加班零食,加班打车报销等

任职要求

  • 计算机相关专业, 具备扎实的编程功底
  • 对技术有激情,勇于接受挑战,思维活跃,善于分析、归纳、解决问题
  • 熟悉前端相关技能(html / javascript / css)

业务方向

职位描述

  1. 负责Web前端项目的架构设计与优化;
  2. 负责核心项目的研发工作;

技术关键词

  • node.js / Sequelize / koa
  • Vue.js / vuex / webpack
  • mapbox-gl / Leaflet / echarts / webgl
  • eslint / prettier

联系方式:

centao@didichuxing.com答复时间(如每日8点答复email等): 两个工作日内

分享基于NodeJS Express Bootstrap 后台管理系统,包括用户,登录,角色,菜单权限等。

最近在想一个web串口项目,有大神给个项目参考吗?

$
0
0

微信图片_20180304154800.png固定了发送的字符串,是WIFI串口 我看过有人用serialport那个包,但是安装不成功 [哭] QQ图片20180305185755.png我win10系统,net.framework 2.0 sdk系统自带 QQ图片20180305190953.png然后visual studio 是2017…

360元6.5年腾讯云服务器

$
0
0

https://cloud.tencent.com/act/campus/group/detail?group=10258

看来可能是 kpi 压力,腾讯年前,年后一直 有优惠活动。。良心云名不虚传。 这次又是,老用户开团,,新用户参团可享。非学生也可以成功购买。 源地址: https://cloud.tencent.com/act/campus/group/index老用户开团成功后可领取 100 元通用代金劵年付 120 元,购买后可以续费两年,然后等组团结束后降低配置就可以总共花 360 元续费到 2023 年。 机房选择成都,降配时,可以返还更多时间。 机房选择成都,降配时,可以返还更多时间。 机房选择成都,降配时,可以返还更多时间。 续费链接: https://cloud.tencent.com/act/campus续费时长随意。 降低配置: https://console.cloud.tencent.com/cvm/index后台控制台 - 更多 - 云主机设置 - 调整配置。选择 1G 内存,会返还时间。 附上操作步骤:

1、登录,用新号登录

2、认证,选择QQ或者微信认证。

3、参团(如果电脑打不开,扫码用手机QQ微信等打开)

4、选择12个月送4个月 支付120块

5、进入https://cloud.tencent.com/act/campus/

6、随便输入学生信息(BUG,不走学信,赶紧上)

7、返回https://cloud.tencent.com/act/campus/

8、续费2次,分别付2次120块

9、进入https://console.cloud.tencent.com/cvm/index

10、看到主机后面 更多-云主机设置-调整配置-改成1G,时间自动延长 我选成都2024-09-25到期。

11、更多-重装系统,可以选择win2008 win2012 还可以选择其他的linux。


[广州]招nodejs 8k-12k,一年经验

$
0
0

地点

广州天河区勤天大厦

公司相关

Appsnt,一家互联网广告公司,公司技术人员占一半,运营妹子占一半左右。法定节假日休假,几乎不加班(一年来我就加过一天)。

待遇

8k-12k 月薪,试用期看表现,最长三个月,熟悉完业务和代码就能转正。至少13薪。

要求

1、一年nodejs开发经验,熟悉koa框架。 2、熟悉es6语法,熟悉async function 和其他异步流程。 3、代码实现优雅,轻微强迫症加分。 4、熟悉mysql、mongodb,掌握索引的正确使用姿势。 5、加分项:熟练使用测试用例。 6、最重要的一点,有自学的习惯和能力。

联系方式

邮箱:abiu@appsnt.com qq:3310653611

【北京】【猿辅导前端招聘】20-40K

$
0
0

关于猿辅导

猿辅导是做在线教育的,目前是K12(中小学)层面的领头羊。潜力无限。

公司目前在大肆扩张,急需各种人才。

待遇:20-40K

要求:有上进心

只用 node child_process 模块如何能提高服务器的响应速度?

$
0
0

master.js

const cp = require('child_process');
const fork = cp.fork;
const os = require('os');
const cpus = os.cpus();
const Net = require('net');

const muti_process = [];
const port = 1337;
const host = '127.0.0.1';
const server = new Net.Server();

for(let i = 0, length = cpus.length; i < length; i ++) {
  muti_process.push(fork('./server.js'));
}

server.listen(port, host, () => {
  muti_process.forEach((process) => {
    process.send('server', server);
  });
  server.close();
  console.log(`server listen on port ${port}`);
});

server.js

const Net = require('net');

const port = process.port || 8080;
const host = '127.0.0.1';

const server = new Net.Server();

function sleep (second) {
  second = parseInt(second, 10);
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve();
    }, second * 1000);
  });
}

let allSpend = 0;

server.on('connection', (socket) => {
  console.log('a new connection has been established.');
  const start = Date.now();
  console.log('start time:', start);
  socket.on('data', (chunk) => {
    console.log(`received data from client...`);
    response(chunk);
  });

  socket.on('end', () => {
    console.log('Closing connection with the client.\n');
  });

  async function response (chunk) {
    console.log(`wait for ${ allSpend } s....`);
    await sleep(3);
    socket.write(chunk.toString());
    socket.end();
    console.log(`request spend ${Math.floor( ( Date.now() - start ) / 1000 ) } s`);
    allSpend += 3;
  }

});

server.on('error', (err) => {
  console.log(err);
});

// server.listen(port, host, () => {
//   console.log(`server is runing on port ${port}\n`);
// });

process.on('message', (message, tcp) => {
  if(message == 'server') {
    tcp.on('connection', (socket) => {
      console.log(`handled by process ${process.pid}`);
      server.emit('connection', socket);
    });
  }
});

master 启动多进程 node 服务, 本意是当请求 localshot:1337/ 的时候, 将单进程时的服务器处理两个请求需要的 6 秒减少为多进程下的 3 秒, 但是目前在多进程下依然是 6 秒,请问各位 child_process 模块能否提高请求响应速度? 如果能应该怎么做? 如果不能, child_process 的本意是什么? 恳求回答 附上项目代码: https://github.com/zhangxiang958/ComputerNetworkLab/blob/master/Socket1_WebServer/source/muti_process/server.js

广州招聘Node.js 开发工程师

$
0
0

你是我们那位有趣的灵魂吗?#

我们需要怎样的你?我们关心的不只是你懂的技术,我们更关心你的能力,更关心你在团队中能否开心工作!

期待你的参与,与我们风雨同路!

集团总部 XO Group

XO Group (资讯集团The Knot) 是美国最大婚尚资讯集团,同时也是纽交所上市集团,股票代码为“XOXO”。旗下包括了网站、杂志、电视节目、相关书籍等诸多媒体资产,业务涵盖了婚尚资讯(theknot.com)、家居生活(thenest.com)、母婴产品(thebump.com),旗下网站月访问量均超过1亿次,注册用户超过300万,是美国在线交易额最大的婚尚网站!

诺特软件 GZ TheKnot

广州诺特软件开发有限公司成立于2009年7月,是XO Group Inc全资控股的软件开发中心,为XO Group的网站和客户提供技术支持。现已成长为2011-2012年度广东重点软件出口企业,2013-2015年广州市技术先进型企业。

薪酬福利

  • 薪资优厚,快速晋升机会,五险一金均齐全(试用期即缴纳);
  • 完善的培训体系,技术大牛导师和女神级别英语外教,优秀同事还能出国参加中美交流计划,让你华丽转身,跻身高手行列!
  • 丰富的生活福利,包括弹性工作制、高大上团队建设活动、轻奢公司旅游,还提供五 彩缤纷的零食柜,满足你的隐藏吃货属性;
  • 月度年度优秀奖励一个不少,同事融洽,领导贴心,跟优秀的人在一起!
  • 地处核心CBD,交通便利,更有开放式的舒适办公环境,
  • 按摩椅、健康体检、健康讲座,全方位呵护你的身心健康!

招聘岗位

Node.js开发工程师

【职位要求】

负责公司核心业务系统后台开发 1.大专或以上学历,计算机或者相关专业毕业 2.1年或以上NodeJS开发经验 3.非常熟悉NodeJS LTS (4.2.x)的特性,有PostgreSQL开发经验 4.熟悉Hapi系列modules,以及Chai系列和Sinon系列的单元测试modules 5.熟悉基于Express框架的开发,熟练使用HTML/CSS/客户端JavaScript 6.有AngularJS开发经验者优先考虑 7.有Rabbit Message Queue的开发经验或者Elasitc Search开发经验者优先考虑 8.能够阅读英文文档,具备一定英文编写能力 9.良好的编程风格,独立思考有高度的工作责任感 10.具有团队开发意识;良好的沟通协调能力,较好的语言表达能力,较好的文字处理能力

Node.js开发工程师 (实习生)###

1.大专或以上学历,计算机或者相关专业毕业(18年毕业生优先!) 2.对Node.js有兴趣 3.有面向对象基础 4.有数据库开发经验 5.了解Javascript

  1. 能理解阅读并编写英文文档
  2. 独立思考有高度的工作责任感 8.具有团队开发意识;良好的沟通协调能力,较好的语言表达能力,较好的文字处理能力

职位待遇

由你的能力及经验决定,待遇从优,详细面谈。

相关链接

[集团美国网站] (http://xogroupinc.com/) [集团中国网站] (http://www.tkcnsoft.com/) 联 系 人:HR & Admin Department 地 址:广州市天河路45号天伦大厦903 电 话 : +8620 3830 3638 - 376 简历可发往:[than@xogrp.com] (than@xogrp.com)

精彩缤纷的公司旅游

完善丰富的内外部培训

开放宽松的办公环境

别开生面的公司活动

mongoose报错:Cannot read property 'toLowerCase' of undefined怎么解决?

$
0
0

用node配合mongoose做了一个数据库系统,前台用的vue,发现报了这个错,不知道怎么来的,如图:366507682-5a96c1ce05cd7_articlex.png至今没有找到原因,求解答

360元六年半腾讯云服务器,老用户可以参与

$
0
0

腾讯云服务器1核2G年付 120 元,购买后可以续费两年,然后等组团结束后降低配置就可以总共花 360 元续费到 2023 年。如果需要降配置的话,建议购买时选择成都节点,降低后可延长至 2024 年,相当于 360 元,6 年半。

老用户方法: 重新申请个新号就可以了。新号认证,主要验证的是微信支付。 只要填写的是和微信绑定银行卡一样的就行。 用你认证过的老号的资料,再输入下身份证和姓名到新号去认证一样可以买。

求支持下,参团链接:https://cloud.tencent.com/act/campus/group/detail?group=11314源地址: https://cloud.tencent.com/act/campus/group/index

另外如果电脑端点击没反应,可以在手机端购买,已成功上车

一款关注写作体验的 markdown 编辑器

$
0
0

废话不多说,直接上地址:

网站地址:MarkText

仓库地址:MarkText

Mark Text

summary.jpg

一款致力于提升用户写作体验的 Markdown 编辑器,其界面简洁,使用 snabbdom 作为其渲染引擎,保证了写作的流畅性。同时Mark Text 提供多种编辑模式。

源码模式

source-code-mode.jpg

Focus 模式

focus-mode.jpg

欢迎大家使用,拍砖。


腾讯云又出活动了,单核 2G1M 配置 5 年 360 元

$
0
0

目前腾讯云新促优惠,单核 2G 内存 1M 带宽,只需 360 块钱即可享有 3年4个月使用期。而如果选择降低配置:单核1G 内存1M 带宽,则可以 360 块钱享有 6年半使用期,该活动 新旧用户均可参与,学生信息可以随便填写,已毕业也可以

步骤:

首先使用手机(只能使用手机),点击链接参团地址,点击 【参团】,然后选择 12 个月时长,付费 120 元即可开通主机。(如果需要降配置的选择 成都节点,等组团结束后降低配置就可以总共花 360 元续费到 2023 年

以优惠的方式续费 2 次:进入 续费链接,可以续费两次,每次最多只能续费 12 个月 (120元)

如需降低配置,等待组团结束再降低配置:点击 【更多】 - 【云主机设置】- 【调整配置】里修改就行。

自己动手实现的promise

$
0
0

看了很多promise的实现,比如这里: 1、http://coderlt.coding.me/2016/12/04/promise-in-depth-an-introduction-2/ 2、https://zhuanlan.zhihu.com/p/25178630感觉很绕,不符合自己的思维习惯,所以想自己动手写一个

在谷歌浏览测试各种情况、错误,看谷歌浏览器是如何反馈的,来改进自己的代码 image.png所以函数名是resolver(只是为了让表现行为更接近chrome),需要的代码是:if(typeof resolver !== ‘function’) throw new TypeError(Promise resolver ${resolver} is not a function); 。。。。。。

源码: https://github.com/fuxingZhang/promiseImplementing还在更新中。。。。 If you like, please help me improve the code

code : function Promise(resolver){ if(typeof resolver !== ‘function’) throw new TypeError(`Promise resolver ${resolver} is not a function`); this.state = ‘pending’ this.value = void 0 try{ resolver(this.resolve.bind(this), this.reject.bind(this)) }catch(error){ this.reject.call(this,error) } }

Promise.prototype.resolve = function(value) { if(this.state !== ‘pending’) return this.value = value this.state = ‘fulfilled’ setTimeout( () => { if(!this.onFulfilled) return this.onFulfilled(value) }, 0) };

Promise.prototype.reject = function(reason){ if(this.state !== ‘pending’) return this.value = reason this.state = ‘rejected’ setTimeout( () => { if(this.onRejected){ this.onRejected(reason) }else{ throw `(in promise) ${reason}` } }, 0) };

Promise.prototype.then = function(fulfilled, rejected){ if ( typeof fulfilled !== ‘function’ && typeof rejected !== ‘function’ ) { return this; } if (typeof fulfilled !== ‘function’ && this.state === ‘fulfilled’ || typeof rejected !== ‘function’ && this.state === ‘rejected’) { return this; } var self = this return new Promise( (resolve, reject) => { if(fulfilled && typeof fulfilled == “function”){ var onFulfilled = function (){ try{ var result = fulfilled(self.value) if(result && typeof result.then === ‘function’){ result.then(resolve, reject) }else{ resolve(result) } }catch(error){ reject(error) } } if( self.state === ‘pending’){ self.onFulfilled = onFulfilled }else{ onFulfilled() } } if(rejected && typeof rejected == “function”){ var onRejected = function (){ try{ var result = rejected(self.value) if(result && typeof result.then === ‘function’){ result.then(resolve, reject) }else{ resolve(result) } }catch(error){ reject(error) } } if( self.state === ‘pending’){ self.onRejected = onRejected }else{ onRejected() } } }) }

/* * the methods don’t in Promise/A+ */

Promise.prototype.catch = function(onRejected){ return this.then(null, onRejected) }

Promise.resolve = function(value){ if(value instanceof this) return value return new Promise( (resolve,reject) => { if(value && typeof value === ‘object’ && typeof value.then === ‘function’){ resolve( value.then( v => v)) }else{ resolve(value) } }) }

Promise.reject = function(reason){ return new Promise( (resolve,reject) => { reject(reason) }) }

呐,也是腾讯云那个活动,拼团了

node源码粗读(8):setImmediate注册+触发全流程解析

$
0
0

本篇文章主要介绍setImmediate底层的实现,主要涉及部分node和libuv源码。

前言

相信稍微对node感兴趣的同学都知道setImmediate触发是在event-loop的check阶段,那么这个setImmediate到底是在哪里实现的注册到uv_check中以及如何触发其中的回调呢?

js入口

如果要想撕开setImmediate的口子,那么最简单粗暴的方式就是直接从./lib/timers这里入手。闲话少说,直接撸代码:

// lib/timers.js setImmediate回调追踪
// ...
const Immediate = class Immediate {
  constructor(callback, args) {
    this._idleNext = null;
    this._idlePrev = null;
    // this must be set to null first to avoid function tracking
    // on the hidden class, revisit in V8 versions after 6.2
    this._onImmediate = null;
    this._onImmediate = callback; // 注意这里
    this._argv = args;
    this._destroyed = false;
    this[kRefed] = false;

    initAsyncResource(this, 'Immediate');

    this.ref();
    immediateInfo[kCount]++;

    immediateQueue.append(this);
  }
  // ...
}
// ...

这是setImmediate所调用的构造函数的一部分内容,从这里可以很明确的看出来我们注册的回调传递给了this._onImmediate
接下来我们看一下最终触发回调的代码:

// lib/timers.js setImmediate回调触发
function runCallback(timer) {
  const argv = timer._argv;
  if (typeof timer._onImmediate !== 'function')
    return promiseResolve(timer._onImmediate, argv[0]);
  if (!argv)
    return timer._onImmediate();
  Reflect.apply(timer._onImmediate, timer, argv); // timer._onImmediate通过apply触发
}

找起来也是轻松加愉快,通过_onImmediate我们很快便找到了触发函数(或者直接找apply)。这样我们可以通过函数名runCallback一层一层向上溯源了。
最终溯源的结果在这里:

// lib/timers.js setImmediate溯源
const {
  Timer: TimerWrap,
  setupTimers,
} = process.binding('timer_wrap');
const [immediateInfo, toggleImmediateRef] =
  setupTimers(processImmediate, processTimers);

向上溯源的函数是通过setupTimers进行注册的,而setupTimers定睛一看:process.binding,内置模块timer_wrap浮出了水面(在这里简单提一下,在最近的pr: timers: refactor timer list processingsetTimeout的触发函数也修改为了使用setupTimers注册)。

node中对setImmediate的处理

processImmediate函数在node中的注册

接上文,我们视线转移到./src/timer_wrap.ccSetupTimers函数中:

// src/timer_wrap.cc
// ...
  static void SetupTimers(const FunctionCallbackInfo<Value>& args) {
    CHECK(args[0]->IsFunction());
    CHECK(args[1]->IsFunction());
    auto env = Environment::GetCurrent(args);

    env->set_immediate_callback_function(args[0].As<Function>());//注意这里
    env->set_timers_callback_function(args[1].As<Function>());

    auto toggle_ref_cb = [] (const FunctionCallbackInfo<Value>& args) {
      Environment::GetCurrent(args)->ToggleImmediateRef(args[0]->IsTrue());
    };
    auto result = Array::New(env->isolate(), 2);
    result->Set(env->context(), 0,
                env->immediate_info()->fields().GetJSArray()).FromJust();
    result->Set(env->context(), 1, toggle_ref_function).FromJust();
    args.GetReturnValue().Set(result);
}

详细介绍一下env->set_immediate_callback_function(args[0].As<Function>())。这句话的来源其实在env.h中:

// src/env.h
// ...
V(immediate_callback_function, v8::Function)    
// ...
#define V(PropertyName, TypeName)                                             \
  inline v8::Local<TypeName> PropertyName() const;                            \
  inline void set_ ## PropertyName(v8::Local<TypeName> value);
  ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V)
#undef V

整体意思是:定义了一个带参宏V,而这个宏在调用的时候会定义一个属性,所以在通过V(immediate_callback_function, v8::Function)调用后,可以实现env->set_immediate_callback_function的调用了,同时还会生成一个成员函数PropertyName(),所以在调用后同时也会使得env->immediate_callback_function成为可调用函数。
args[0]在这里指的便是processImmediate,所以通过SetupTimers可以使processImmediate这个函数最终注册到node中。

libuv中对setImmediate的处理

processImmediate函数在libuv中的注册

视线转移到src/env.cc中,不知道大家是否还记得在第一章一个简单的nodejs文件从运行到结束都发生了什么中曾经一笔带过Environment::Start。没错,setImmediate就是在Environment::Start中注册到libuv的,接下来看代码:

// src/env.cc
// ...
void Environment::Start(int argc,
                        const char* const* argv,
                        int exec_argc,
                        const char* const* exec_argv,
                        bool start_profiler_idle_notifier) {
  HandleScope handle_scope(isolate());
  Context::Scope context_scope(context());

  uv_check_init(event_loop(), immediate_check_handle());
  uv_unref(reinterpret_cast<uv_handle_t*>(immediate_check_handle()));

  uv_idle_init(event_loop(), immediate_idle_handle());

  uv_check_start(immediate_check_handle(), CheckImmediate);
  // ...
}

有关于immediate的总共有四句,接下来我们逐个详细介绍一下:

  • uv_check_init(event_loop(), immediate_check_handle());这一句主要是初始化uv_check的handle;
  • uv_unref(reinterpret_cast<uv_handle_t*>(immediate_check_handle()));这一句主要是解除uv_check的handle在event-loop中的引用,因为我们希望在event-loop中没有活跃handle的时候自动退出;
  • uv_idle_init(event_loop(), immediate_idle_handle());这里涉及到uv_idle的概念,uv_idle总是在uv_prepare阶段之前运行,在这里是用uv_idle_init方法对uv_idle的handle进行初始化;
  • uv_check_start(immediate_check_handle(), CheckImmediate);在这里,真正的把上文提到的processImmediate通过函数CheckImmediate注册到了uv_check中

uv_idle简介

在这里涉及到了uv_idle系列api,那么就给大家稍作介绍:
相信很多人都看过node官方那张经典的event-loop的图,在idle、prepare阶段的下一阶段是poll。而poll阶段是不断轮训执行callback,所以是会阻塞的。具体的调用代码是uv__io_poll(loop, timeout);,这里的timeout就是超时时间,具体设置timeout的代码可以看这里:

int uv_backend_timeout(const uv_loop_t* loop) {
  if (loop->stop_flag != 0)
    return 0;
  if (!uv__has_active_handles(loop) && !uv__has_active_reqs(loop))
    return 0;
  if (!QUEUE_EMPTY(&loop->idle_handles))
    return 0;
  if (!QUEUE_EMPTY(&loop->pending_queue))
    return 0;
  if (loop->closing_handles)
    return 0;
  return uv__next_timeout(loop);
}

可以看到在一些情况下超时时间可以是0——即可以直接跨过poll阶段到达下一个check阶段,而check阶段就是setImmediate执行的阶段。这些可以跨过poll阶段的情况有:

  • 使用stop_flag直接强制跨过;
  • event-loop中没有活跃的handle且没有活跃的请求时;
  • idle不为空的时候;
  • pending_queue不为空的时候(uv__io_init会初始化pending_queue);
  • 关闭handle的时候;

setImmediate正是利用了idle,实现了对poll阶段的跨越。

setImmediate与uv_idle

在src/env.cc中有一个比较不起眼的api–ToggleImmediateRef:

void Environment::ToggleImmediateRef(bool ref) {
  if (ref) {
    // Idle handle is needed only to stop the event loop from blocking in poll.
    uv_idle_start(immediate_idle_handle(), [](uv_idle_t*){ });
  } else {
    uv_idle_stop(immediate_idle_handle());
  }
}

不知道大家还记得上文提到过的SetupTimers吗,里面有一行代码:

static void SetupTimers(const FunctionCallbackInfo<Value>& args) {
// ...
 auto toggle_ref_cb = [] (const FunctionCallbackInfo<Value>& args) {
      Environment::GetCurrent(args)->ToggleImmediateRef(args[0]->IsTrue());
    };
//...
}

结合这两个函数,很容易得出结论:setImmediate通过函数ToggleImmediateRef对uv_idle进行开关的控制,开的时候可以直接越过poll阶段,关的时候则执行poll阶段

setImmediate的执行

刚才聊到了,通过uv_check_start(immediate_check_handle(), CheckImmediate);setImmediate的上层函数processImmediate通过CheckImmediate注册到了uv_check中。接下来我们看下CheckImmediate

// src/env.cc
void Environment::CheckImmediate(uv_check_t* handle) {
  Environment* env = Environment::from_immediate_check_handle(handle);
  // ...
  do {
    MakeCallback(env->isolate(),
                 env->process_object(),
                 env->immediate_callback_function(),
                 0,
                 nullptr,
                 {0, 0}).ToLocalChecked();
  } while (env->immediate_info()->has_outstanding());

  if (env->immediate_info()->ref_count() == 0)
    env->ToggleImmediateRef(false);
}

相信如果读者从头到尾贯穿下来的话,这里已经很明了了,通过一个do...while...实现了对immediate_callback_function的调用,即调用了js中的processImmediate进而实现了setImmediate的运行。在运行完后,通过env->ToggleImmediateRef(false);实现对uv_idle的停止,进而使得poll能阻塞处理回调。

结语

通过上面的分析,读者基本可以清晰了解到setImmediate的整体注册和触发流程。而真正触发evnet-loop的,则是在src/node.cc中:

// src/node.cc
// ...
inline int Start(Isolate* isolate, IsolateData* isolate_data,
                 int argc, const char* const* argv,
                 int exec_argc, const char* const* exec_argv) {
  // ...
  Environment env(isolate_data, context);
  env.Start(argc, argv, exec_argc, exec_argv, v8_is_profiling);// 触发Environment::Start
  // ...
  {
    // ..
    do {
      uv_run(env.event_loop(), UV_RUN_DEFAULT); // 触发event-loop
    } while (more == true);
  }
  // ...
}

by 小菜
原文地址:https://github.com/xtx1130/blog/issues/19,欢迎star和watch。如果叙述或者逻辑有问题,还请大家斧正。

腾讯云的那个,我这个还剩16小时啊,开团了

Viewing all 14821 articles
Browse latest View live