JS的异步世界

前言

JS的异步由来已久,各种异步概念也早早堆在开发者面前。可现实代码中,仍然充斥了各种因异步顺序处理不当的bug,或因不好好思考,或因不了解真相。今天,就特来再次好好探索一番JS的异步世界。

01 异步的由来--单线程

上世纪末,互联网仍处于极慢速时代,穿梭于客户端与服务端的请求,对于时间的耗费是如此的奢侈。而即将面世的LiveScript,便被网景公司考虑同时在浏览器和服务端使用,在浏览器端对表单进行校验,从而提高表单提交效率。为了将这一脚本语言推向市场,网景与sun联合开发,最终以Java冠名为JavaScript。

刚面世的JavaScript,是为网页设计人员准备的,不需要太复杂的语言设计,能简单上手,自然就是最好的。

于是,单线程,弱类型,一开始就成为了JavaScript的基因。而其中的单线程,便是最戏剧性的存在,Ryan Dahl因为JavaScript是单线程语言,从而选择了js开发了轻量级服务器(nodejs),使得js从浏览器端延伸到服务器。随着JS开发队伍和程序复杂度的同步发展,异步处理成为了JS程序的重中之重。

02 JS是一个充满异步的世界

先来导入几个异步的常见场景

dom用户输入响应

ducument.addEventListener('click', function(){})

Ajax

$.ajax(<url>, function() {})

定时/延时

setTimeout(function() {}, 1000)
setInterval(function() {}, 1000)

文件读取

var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function() {}

以上的场景基本有个共同特性,耗时!

举个栗子,我们去银行取钱,当人很多时,如果还是排队模式,会耗费很多时间(同步模式)。于是设立了取号机,取了号,不用排队,在一旁坐着,安心打开电脑写个文档,等叫号后再去办业务(异步模式)。

同理,由于单线程的特性,当JS应用越来越复杂,耗时的程序如果以同步来进行,就会阻塞js的单线程,如大水冲过狭窄的河道,势必决堤。那JS是怎么开拓导流渠道的呢?其实在js的单线程(主线程)背后,规律的运行了很多线程:

  1. dom事件处理线程
  2. http请求线程
  3. 定时器线程
  4. ...

这些线程就充当了JS大江的小河道,当短时有大流量时,接纳吸收,将过滤处理后的正常水流,再汇入JS主干道。

与其说JS是单线程,不如说JS是有着自动化多线程处理的主线程。无需手动编码介入新开线程,切换线程,消息同步等冗繁的处理。专用线程会接管相关任务,并将处理结果送回主线程进行顺序处理。

说到这里稍微提一下web worker,虽然是自定义的多线程,最终还是子线程地位,仍旧将处理完成的结果以回调函数方式汇入到主线程进行异步处理。

03 异步处理一般流程

先看以下代码,异步模式开始了

var img = new Image()
var imgLoadCallback = function() {}
img.src = 'http://????'
img.onload = callback

“http君,麻烦帮取一个图片数据,好了后交给imgLoadCallback君。” — js主线程老大
“任务收到,您先忙,图片请求交给我了,好了之后我叫imgLoadCallback君到休息室排队,您空了通知下 Event Loop巡检官。” — http请求线程

img.src = 'http://????'
img.onload = imgLoadCallback

“图片已取到,imgLoadCallback君去休息室排队等候吧!” — http请求线程

imgLoadCallback入栈JS任务队列

“刚好忙完手上的事情了,Event Loop君,帮看下休息室有没有人排队” — JS主线程老大
“老大,已把等候者imgLoadCallback叫过来处理任务” — Event Loop巡检官

执行imgLoadCallback

“事情都交给合适的人去办了,突然就清闲下来了,老大就是要这样当啊,嘿嘿嘿… Event Loop君,定时看下休息室有没有人排队吧… ” — JS主线程老大

JS主线程通过Event Loop读取任务队列

讲完故事,再来看这张异步示意图,是否能理解了?

image

04 回调处理工具的进化

从前面的篇章已经能看出来了,异步处理的结果是通过回调放置到任务队列转接到主线程中的。

北京猿人刀跟火种,这么写异步回调,看上去也能令人接受。

$.ajax(
    url: '自家香蕉树林',
    data: {
        picker: '猴子A'
    },
    success: function(data) {
        $.ajax(
            url: '隔壁老孙家桃林',
            data: {
                exchanges: data.香蕉,
                buyer: '猴子A'
            },
            success: function(data) {
                console.log('向本猴王进贡', data.桃子)
            }
        )
    }
)

进化成人类后交易过程变的复杂了,于是就变成回调地狱,传说中的callback hell

$.ajax(
    url: '自家香蕉树林',
    data: {
        picker: '老王'
    },
    success: function(data) {
        $.ajax(
            url: '集市贩卖',
            data: {
                goods: data.香蕉,
                seller: '老王'
            },
            success: function(data) {
                $.ajax(
                    url: '隔壁老李桃子铺',
                    data: {
                        exchanges: data.钱,
                        buyer: '老王'
                    },
                    success: function(data) {
                        console.log('向本王进贡', data.桃子)
                    }
                )
            }
        )
    }
)

于是发明了铁器promise,解决回调地狱之痛

$.ajax(
    url: '自家香蕉树林',
    data: {
        picker: '老王'
    }
)
.then(function(data) {
    return $.ajax(
        url: '集市贩卖',
        data: {
            goods: data.香蕉,
            seller: '老王'
        }
    )
})
.then(function(data) {
    return $.ajax(
        url: '隔壁老李桃子铺',
        data: {
            exchanges: data.钱,
            buyer: '老王'
        }
    )
})
.then(function(data) {
    console.log('向本王进贡', data.桃子)
})

关于promise的升级版async、await,本篇不多说了,理念上基本一致。

继续...

这下一次命令,只会来供给本王一次桃子,每次都要发令,好麻烦,得下个令让老王每天去卖香蕉买桃子,给我月供100个,于是就发生了以下的故事

var contributeTime;
setInterval(function(){
    $.ajax(
        url: '自家香蕉树林',
        data: {
            picker: '老王'
        }
    )
    .then(function(data) {
        return $.ajax(
            url: '集市贩卖',
            data: {
                goods: data.香蕉,
                seller: '老王'
            }
        )
    })
    .then(function(data) {
        return $.ajax(
            url: '隔壁老李桃子铺',
            data: {
                exchanges: data.钱,
                buyer: '老王'
            }
        )
    })
    .then(function(data) {
        var currentTime = new Date().getTime();
        if (!contributeTime || (currentTime - contributeTime > '月')) {
            console.log('向本王进贡', [data.桃子,…]); //length=100
            currentTime = contributeTime;
        }
    })
}, '天')

这过程,好像也太不优雅了点。

ReactX的JS版,RxJs来了,将异步看作为单点,将其扩展了时间线,作为流来处理。所以对于一次又一次的进贡,都可进行时序管理,于是整个过程变成这样:

import { ajax } from 'rxjs/ajax'; //此处特别写引入,目的为不与jquery.ajax混淆
import { interval } from 'rxjs';
const ob = interval('天');
const peachPay = ob
    .pipe(switchMap(x => ajax.post('自家香蕉树林', {picker: '老王'})))
    .pipe(switchMap(data => ajax.post('集市贩卖', {seller: '老王', goods: data.香蕉})))
    .pipe(switchMap(data => ajax.post('隔壁老李桃子铺', {buyer: '老王', exchanges: data.钱})))
    .pipe(throttle(data => interval('月')))
    .subscribe(data => console.log(`每月收到月供:${data.桃子.length}个${data.桃子}`));

整个过程顺着管道不断变换处理,就是一条全自动流水线!然鹅,然鹅,并一定每月就能供出100个桃子啊,万一遇到农灾,或者经济萧条…

以上例子仅提供思路,且读且珍重!

05 比工具更重要的,是理解

前端开发中,诸多剪不断理还乱的偶现bug来源于异步处理的顺序混乱。即便是异步处理工具越来越先进,由于代码层面的顺序和真实执行顺序的不一致,也还是容易一不小心犯错误。

异步处理工具不是万能的,还是需不断将异步原理内化入思维模式中,种码的时候,就需清晰的知道该段代码会什么时候结出果实。

再注:以上代码未经运行验证,仅示意流程与思路。望各路大神多多包涵!如有思路上都提供错误的,求拖出去打板子!

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

推荐阅读更多精彩内容