Node.js异步控制流:回调、事件、Promise和async/await

前言

对于Node.js的异步控制流,目前共计有四种常用的方式。较为经典的为callbackEventEmitter;在ES6中,加入了Promise;在ES7中加入了async/await。下面就逐个分析一下这四种常用的异步控制。

callback形式的异步控制

对于callback形式,即采用回调函数。在理解上,就是函数将任务分配出去,当任务完成之后,然后根据执行结果来进行相应的回调。可以看下面一个例子:

function sleep(ms, callback){
    setTimeout(function(){
        callback("finish")  //执行完之后,返回‘finish’。
    }, ms);
}

sleep(1000, function(val){
    console.log(val);
});

//输出结果:finish。

这种形式十分容易理解,但是能够解决大部分的问题。但是却存在着致命的缺点。可以想象,如果需要在一段代码中调用多次sleep()函数,将会出现下面的情况:

var i = 0;//记录sleep()函数调用的次数
function sleep(ms, callback){
    setTimeout(function(){
        if(i < 2){
            i++;
            callback("finish", null);  
        }else{
            callback(null, new Error('i大于2'));
        }
    }, ms);
}
//第一次调用
sleep(1000, function (val, err) {
    if (err) console.log(err.message);
    else {
        console.log(val);
        //第二次调用
        sleep(1000, function (val, err) {
            if (err) console.log(err.message);
            else {
                console.log(val);
                //第三次调用
                sleep(1000, function (val, err) {
                    if (err) console.log(err.message);
                    else {
                        console.log(val);
                    }
                });
            }
        });
    }
});

//输出结果分别为:finish,finish,i大于2。

由上面的例子可以看出,假如需要多次调用sleep()函数时,就会进行多次的嵌套。这种嵌套虽然可以使用,但是在代码的阅读、维护和美观上面,都是反人类出现的。所以,在新标准ES6和ES7发布之后,将会逐渐摒弃这种写法。

事件监听形式的异步控制

对于callback的改进就是使用事件监听的形式进行操作:每次调用异步函数都会返回一个EvetEmitter对象。在函数内部,可以根据需求来触发不同的事件。对不同的事件进行监听,然后做出相应的处理。具体代码如下:

var i = 0;
var events = require('events');
var emitter = new events.EventEmitter();//创建事件监听器的一个对象
function sleep(ms) {
    setTimeout(function () {
        i++;
        if (i < 2) {
            emitter.emit('done', 'finish');//触发事件'done'
        } else {
            emitter.emit('error', new Error('i大于2'));//触发事件'error'
        }
    }, ms);
}
var emit = sleep(1000);
//监听事件'done'
emitter.on('done', function(val){
    console.log(val);
});
//监听事件'error'
emitter.on('error', function(val){
    console.log(val);
});

通过事件监听的形式进行异步处理,可以更加直观和多样性。但是和callback类似,并没有解决嵌套的问题。例如需要调用多次sleep()函数,依然需要在函数emitter.on('done', function(val))中进行嵌套。

Promise对象进行异步控制处理

比较Promise和EventEmitter,可以发现两个十分明显的区别。对于EventEmitter,我们可以根据自己的需求来定义多种监听事件,然后对不同的事件进行不同的处理;对于promise,则可以对将异步操作的结果进行返回,然后根据返回值来进行相应的操作。对于promise的详细学习,可以通过在MDN查阅详细且最新的资料。
代码如下:

let num = 1
function sleep(ms) {
  //返回一个Promise对象
  return new Promise(function (resolev, reject) {
    setTimeout(function () {
      if (num < 1) {
        num++
        //如果成功,对resolve处理
        resolev(new Error('finish'))
      } else {
        //如果失败,对reject操作
        reject('i大于2')
      }
    }, ms)
  })
}
sleep(300).then(value => {
  console.log(value)
  return sleep(300)
}).then(value => {
  console.log(value)
  return sleep(300)
}).then(value => {
  console.log(value)
  return sleep(300)
}).catch(function (error) {
  console.log(error)
})

和callback与EventEmmitter相比,Promise将异步处理的嵌套函数进行了展开,更利于阅读和理解。当promise链中的任意一个函数出错都会直接抛出到链的最底部,所以我们统一用了一个catch去捕获,每次promise的回调返回一个promise,这个promise把下一个then当作自己的回调函数,并在resolve之后执行,或在reject后被catch出来。

async/await解决异步控制

虽然Promise将嵌套函数进行了展开,但是依然是链式函数,并没有解决回调本身。因此在最新的ES7中,引入了async/await函数,来解决这个问题。async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
代码如下:

let num = 1
function sleep(ms) {
  //返回一个Promise对象
  return new Promise(function (resolev, reject) {
    setTimeout(function () {
      if (num < 3) {
        num++
        //如果成功,对resolve处理
        resolev('finish')
      } else {
        //如果失败,对reject操作
        reject(new Error('i大于2'))
      }
    }, ms)
  })
}
//用关键词async修饰函数
async function asyncSleep() {
  try {
    let value
    //用await来修饰函数
    value = await sleep(300)
    console.log(value)
    value = await sleep(300)
    console.log(value)
    value = await sleep(300)
    console.log(value)
  } catch (error) {
    console.log(error)
  }
}
//调用函数
asyncSleep()

在上面的函数中,我们进行逐步分析

  • 我们首先将setTimeout函数进行封装,让其返回Promise对象。
  • 然后声明一个用async修饰的函数,在里面调用sleep()函数。
  • 对于每次sleep()函数,我们用await关键词进行修饰,表示下面的操作需要进行异步处理。
  • 声明一个value变量来接受sleep()函数返回的Promise对象。
  • 最后用catch来对异常进行处理。

总的来说async/await是promise的语法糖,但它能将原本异步的代码写成同步的形式,try...catch也是比较友好的捕获异常的方式所以在今后写node的时候尽量多用promise或者async/await,对于回调就不要使用了,大量嵌套真的很反人类。

参考资料

node.js异步控制流程 回调,事件,promise和async/await
ECMAScript 6 入门, 阮一峰

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

推荐阅读更多精彩内容

  • 异步编程对JavaScript语言太重要。Javascript语言的执行环境是“单线程”的,如果没有异步编程,根本...
    呼呼哥阅读 7,296评论 5 22
  • 本文首发在个人博客:http://muyunyun.cn/posts/7b9fdc87/ 提到 Node.js, ...
    牧云云阅读 1,679评论 0 3
  • 弄懂js异步 讲异步之前,我们必须掌握一个基础知识-event-loop。 我们知道JavaScript的一大特点...
    DCbryant阅读 2,693评论 0 5
  • javascript的运行机制是单线程处理,即只有上一个任务完成后,才会执行下一个任务,这种机制也被称为“同步”。...
    我是xy阅读 3,869评论 1 6
  • 异步编程模式在前端开发过程中,显得越来越重要。从最开始的XHR到封装后的Ajax都在试图解决异步编程过程中的问题。...
    SCQ000阅读 2,745评论 1 51