JAVA程序员如何转node_02

前言

本文的前置文章

JAVA程序员如何转node_01

不知道看了上一篇之后,是否对node形成了初步的印象。是的,异步编程的实现给cpu处理IO的效率带来了极大的提高。但是,异步编程在发展的过程可远没有我说的那么简单,关于异步还有很多你需要知道的地方,这一篇我将继续和你一起探究。

本文涉及到的知识点

  1. Promise 一个可以将回调转换成async的语法

  2. async 和 await 上次提到的两个关键字


node中的异步处理逻辑

我会从两个例子来介绍node的异步,希望你看完能对他有一个直观的感受。这是两个很有意思也很典型的例子,如果你觉得有点疑惑,那也是非常正常的,可以试着搜索一下其他的材料。

1、setTimeout。

先从setTimeout这个函数说起,setTimeout(handle,timeout)是javaScript中一个典型的异步函数,其中handle是一个回调函数,这个函数的作用是经过了timeout毫秒之后回来调用这个handle函数。上一篇中说过,异步函数都是用这些回调函数来实现的。so,node的底层实际上就是这么一些回调函数,学习异步,先从这些回调函数开始。

我们先对这样一段代码进行实验

console.log('1');  //1
setTimeout(()=>{   //2
  console.log('2');
},1000);
console.log('3');  //3
setTimeout(()=>{   //4
  console.log('4');
},0);
setTimeout(()=>{   //5
  console.log('5');
},0);
console.log('6');  //6

大家可以先想一下可能会输出什么。

直觉来看,函数往下走,首先输出1,第二个函数那里会让console.log('2')延后一秒,然后输出3,第四个函数那让console.log('4')延后0秒,那应该就是直接输出了,然后再输出5,再输出6,隔了1秒之后最后输出2。

可以实验一下这段代码,他的最终输出是:1 3 6 4 5 2

嗯?6要比4和5早输出?为什么会这样?

实际上,在node中执行顺序是这样的,在执行到第四个函数的时候(前面已经输出了1和3),这时候由于setTimeout是一个异步函数,他会把里面的console.log('4')放进一个事件树中,等所有代码都执行完了、时间到了、再来执行他,函数5同理。所以,这里即使他的timeout参数是0,也不会马上就执行。

所以学习node的异步,你首先要尽量让自己免受以前同步写法的影响。要知道,他并不是像java一样,从一个入口进去,一直运行到结束。在node这里,他会额外维护着一个事件队列。

2、async与await

再来看一下这段代码

async function func(){
    console.log(1);
    await func1();
    console.log(3);
    await func2();
    console.log(5);
}
async function func1(){
    console.log(2);
}
async function func2(){
    console.log(4);
}
func();

输出的结果是 1 2 3 4 5 。

可见在在单个async函数内部中,只要调用了await进行阻塞,内部就是严格的顺序执行的。

但是多个async函数之间可就不是这样。

//这个函数的意思是暂停ms毫秒,然后返回num。 
async function sleep(num,ms){//下面会解释为什么这么写
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{resolve(num)}, ms);
    });
}
async function print(num,ms){
    while(true){
        let res = await sleep(num,ms);
        console.log(res);
    }
}
async function application(){
    print(0,2000);   //1
    print(100,5000);   //2
}
application();

可以跑一下这个例子。他的输出结果是

11
12
22
13
14
23
15
16
17
24

首先,application调用了两个async函数,没有用await来控制他们的顺序执行,所以第一个print不会影响到第二个print。

第二,在单个async里,由于有await阻塞sleep,所以一个sleep执行完了才接着执行下一个sleep,不会出现一瞬间执行很多个setTimeout的效果。

有了这种语法,即使只有一个单线程,但实现上似乎就变成了多线程。这就是异步编程的奇异之处。


promise与async

在上面那个例子中,应该有人会好奇这里为什么要这么写

async function sleep(num,ms){
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{resolve(num)}, ms);
    });
}

这里是为了将setTimeout这个回调函数转变为async这种同步写法。

同上面所说的,如果你在一个函数中多次调用异步函数,那他在直观上是跟多线程一样并行执行的。

function application(){
    setTimeout(()=>{console.log('hello')},5000);
    setTimeout(()=>{console.log('hello')},5000);
    setTimeout(()=>{console.log('hello')},5000);
}
application();

这个程序执行后,会在过了5秒之后,连续输出3个helle。那如果我想每隔5秒输出一个呢?不好意思,你只能用promise将他转化为async可以识别的内容。转化完了之后再用await来阻塞执行。

async function printAfter5s(){
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{console.log('hello')}, 5000);
    });
}
async function application(){
    await printAfter5s();
    await printAfter5s();
    await printAfter5s();
}
application();

promise就是回调函数转化为async的一个桥梁。

当然他也有其他的写法如thePromise.then().then().reject()这种,但我一般都不用,感觉最好的还是让他和async配合使用,比较直观。

如何封装一个回调函数

一般我是这么封装的

function myFunction(){  //这里和async function myFunction没有区别
   return new Promise(function(resolve, reject) {
     // ... some code
     if (/* 异步操作成功 */){
       resolve('hello world');
     } else {
       reject(new Error('a error'));
     }
   });
}

首先,一个函数如果返回的是一个Promise对象,那他默认就是一个async函数。

其次,resolve函数和reject函数里面的参数,会分别被这样接收

async function application(){
   try{
      let res = await myFunction();
      console.log(res);
   } catch(err) {
      console.log(err.message);
  } 
}

如果异步操作成功,那就会输出hello world,如果失败了就输出一个 a error。

一般来说,一个规范的回调函数(setTimeout除外),都会提供一个默认的回调函数给你,比如mysql的官方示例中查询是这样的。

connection.query('SELECT 1 + 1 AS solution', function (error, results, fields) {
  if (error) throw error;
  console.log('The solution is: ', results[0].solution);
});

数据库首先会进行查询,如果查询成功,results中就会有数据;如果失败了(比如你的查询语句有问题/没有这个表),那error就会有数据(值是一个Error对象)

这是我们就可以对他进行简单的封装

function query(sql){
   return new Promise((resolve,reject)=>{
      connection.query(sql, function (error, results, fields) {
         if (error) reject(error);
         resolve(results);
      });
   });
}

这样,我们就可以用await query('SELECT 1 + 1 AS solution');来调用他了。

规范来讲,大多数的异步函数提供的回调函数,其参数都是error放在第一位,结果放在error后面,有这些规范的函数,我们都可以使用utils.promisify来将他转变为async

比如最常见的文件读取函数

const util = require('util');
const fs = require('fs');
const readFileAsync = util.promisify(fs.readFile);
async function application(){
   try{
      let res = await readFileAsync('e://a.txt');
   } catch(err) {
      console.log(err);
   }
}
application();

需要注意的是,await 需要放在async函数里面,所以每次我都得套一层函数在上面,写起来稍显复杂,在实际编写的时候其实不会这么复杂。

这一篇就先讲到这里,希望对你有些帮助。

如果觉得有用的话可以留下你的点赞和评论,蟹蟹

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容