Javascript中的异步编程

前言

最近,小伙伴S 问了我一段代码:

const funB = (value) => {

    console.log("funB "+ value);

};


const funA = (callback) => {

    ...

    setTimeout(() => {

        typeof callback === "function" && callback("is_ok!");

    }, 1000);

}


funA(funB);

他不太理解这段代码中,funB 函数作为 funA 函数的参数这样的写法。从语义上看,callback 的意思是回调,那么是说 funB 是 funA 的回调嘛?

我给他解释说,funB 函数的确是 funA 函数的回调,它会等待 funA 中前面的语句都执行完,再去执行。这是一种异步编程的写法。

小伙伴S 还是有点不太理解:异步编程是什么?除了回调函数之外,异步编程还有哪些?

别急,让我们先从概念入手,再逐个理解异步编程中的方法,看看它的前世今生。

什么是异步?

所谓"异步"(Asynchronous),可以理解为一种不连续的执行。简单地说,就是把一个任务分成两段,先执行第一段,然后转而执行其他任务,等接到通知了,再回过头执行第二段。

我们都知道,JavaScript是单线程的。而异步,对于JavaScript的重要性,则体现在非阻塞这一点上。一些常见的异步有:

onclick 在其事件触发的时候,回调会立即添加到任务队列中。

setTimeout 只有当时间到达的时候,才会将回调添加到任务队列中。

ajax 在网络请求完成并返回之后,才将回调添加到任务队列中。

接下来,我们一起来看看Javascript中的异步编程,具体有哪几种。

实现异步编程的方法

一、回调函数

上面不止一次提到了回调函数。它从概念上说很简单,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。它是异步编程中,最基本的方法。

举个例子,假定有两个函数 f1 和 f2,后者等待前者的执行结果。顺序执行的话,可以这样写:

f1();

f2();

但是,如果 f1 是一个很耗时的任务,该怎么办?

改写一下 f1,把 f2 写成 f1 的回调函数:

const f1 = (callback) => {

    setTimeout(() => {

        typeof callback === "function" && callback();

    }, 1000);

}

f1(f2);

二、事件监听

onclick 的写法,在异步编程中,称为事件监听。它的思路是:如果任务的执行不取决于代码的顺序,而取决于某个事件是否发生,也就事件驱动模式。

还是 f1 和 f2 的例子,为了简化代码,这里采用jQuery的写法:

// 为f1绑定一个事件,当f1发生done事件,就执行f2

f1.on('done', f2);


// 改写f1

function f1(){

    setTimeout(() => {

        // f1的任务代码,执行完成后,立即触发done事件

        f1.trigger('done');

    }, 1000);

}

它的优点是:比较容易理解,耦合度降低了。可以绑定多个事件,而且每个事件还能指定多个回调函数。

缺点是:整个程序都会变为由事件来驱动,流程会变得很不清晰。

三、发布/订阅

这是一种为了处理一对多的业务场景而诞生的设计模式,它也是一种异步编程的方法。vue中MVVM的实现,就有它的功劳。

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

下面的例子,采用的是 Morgan Roderick 的 PubSubJS ,这是一个无依赖的JavaScript插件:

import PubSub from 'pubsub-js';


// f2向 'PubSub' 订阅信号 'done'

PubSub.subscribe('done', f2);


const f1 = () => {

    setTimeout(() => {

        // f1执行完成后,向 'PubSub' 发布信号 'done',从而执行 f2

        PubSub.publish('done');

    }, 1000);

};

f1();


// f2 完成执行后,也可以取消订阅

PubSub.unsubscribe("done", f2);

这种模式有点类似于“事件监听”,但是明显优于后者。因为,我们可以通过查看“消息中心”,了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。

四、Promise对象

接下来,我们聊聊与ajax相关的异步编程方法,Promise对象。

Promise 是由 CommonJS 提出的一种规范,它是为了解决回调函数嵌套,也就是回调地狱的问题。它不是新的语法功能,而是一种新的写法,允许将回调函数的横向加载,改成纵向加载。它的思想是,每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。

继续改写 f1 和 f2:

const f1 = () => {

    return new Promise((resolve, reject) => {

        let timeOut = Math.random() * 2;

        setTimeout(() => {

            if (timeOut < 1) {

                resolve('200 OK');

            } else {

                reject('timeout in ' + timeOut + ' seconds.');

            }

        }, 1000);

    });  

};


const f2 = () => {

    console.log('start f2');  

};


f1().then((result) => {

    console.log(result);

    f2();

}).catch((reason) => {

    ...

);

例子中,用随机数模拟了请求的超时。当 f1 返回 Promise 的 resolve 时,执行 f2。


Promise的优点是:回调函数变成了链式的写法,程序的流程可以看得很清楚。还有就是,如果一个任务已经完成,再添加回调函数,该回调函数会立即执行。所以,你不用担心是否错过了某个状态。


缺点就是:编写和理解,都相对比较难。


五、Generator


generator(生成器)是 ES6 标准引入的数据类型。它最大特点,就是可以交出函数的执行权(即暂停执行),是协程在 ES6 中的实现。


看上去它像一个函数,定义如下:

function* gen(x) {

  var y = yield x + 2;

  return y;

}

它不同于普通函数,函数名之前要加星号(*),是可以暂停执行的。

整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。用 yield 语句注明异步操作需要暂停的地方。

我们来看一下 Generator 函数执行的过程:

var g = gen(1);


// { value: 3, done: false }

g.next();

// { value: undefined, done: true }

g.next();

上面代码中,调用 Generator 函数,会返回一个内部指针(即遍历器 )g 。这是 Generator 函数不同于普通函数的另一个地方,即执行它不会返回结果,返回的是指针对象。调用指针 g 的 next 方法,会移动内部指针(即执行异步任务的第一段),指向第一个遇到的 yield 语句,上例是执行到 x + 2 为止。

换言之,next 方法的作用是分阶段执行 Generator 函数。每次调用 next 方法,会返回一个对象,表示当前阶段的信息( value 属性和 done 属性)。value 属性是 yield 语句后面表达式的值,表示当前阶段的值;done 属性是一个布尔值,表示 Generator 函数是否执行完毕,即是否还有下一个阶段。

六、async/await

这是 ES8 中提出的一种更优雅的异步解决方案,灵感来自于 C# 语言。具体可前往 细说 async/await 相较于 Promise 的优势 ,深入理解其原理及特性。

来看个例子,要实现一个暂停功能,输入 N 毫秒,则停顿 N 毫秒后才继续往下执行。

const sleep = (time) => {

    return new Promise((resolve, reject) => {

        setTimeout(() => {

            resolve();

        }, time);

    })

};


const start = async () => {

    console.log('start');

    // 在这里使用起来就像同步代码那样直观

    await sleep(1000);

    console.log('end');

};


start();

控制台先输出 start,稍等 1 秒后,输出结果 ok,最后输出 end。

解析一下上述代码:

async 表示这是一个async函数,await 只能用在这个函数里面。

await 表示在这里等待 promise 返回了结果,再继续执行。

使用起来,就像写同步代码一样地优雅。

总结

JavaScript的异步编写方式,从 回调函数 到 async/await,感觉在写法上,每次都有进步,其本质就是一次次对语言层抽象的优化。以至于现在,我们可以像同步一样地,去处理异步。

换句话说就是:异步编程的最高境界,就是根本不用关心它是不是异步


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

推荐阅读更多精彩内容