js异步解决方案 --- 回调函数 vs promise vs generater/yield vs async/await

javascript -- 深度解析异步解决方案

高级语言层出不穷, 然而唯 js 鹤立鸡群, 这要说道js的设计理念, js天生为异步而生, 正如布道者朴灵在 node深入浅出--(有兴趣的可以读一下, 很有意思^_^) , 异步很早就存在于操作系统的底层, 意外的是,在绝大多数高级编程语言中,异步并不多见,疑似被屏蔽了一搬. 造成这个现象的原因或许令人惊讶, 程序员不太适合通过异步来实现进行程序设计 ^_^.

异步的理念是很好的, 然而在程序员编程过程中确实会出现一些问题, 并不是这种理念不容以让人接受, 而是当有大量的异步操作时会让你的代码可读性降低, 其中回调函数异步编程容易产生毁掉陷阱, 即 callback hell--(不要急, 后面会详细讲解)

然而 js 社区从为停止其脚步, 最新的 ES7 所推出的 async/await 终极异步解决方案, 说终极可能有所不严禁, 然而它确实已经完全将原来通过模块侵入式的异步编程解脱出来, 可以让程序员以接近传统意义上的函数调用实现异步编程, 这是 js 里程碑式变革中极其重要的一部分.

Javascript异步编程解决方案历史与方法

ES 6以前:

  • 回调函数
    回调函数是最原始的异步编程方案, 上篇文章已经讲述, 这里不再累赘, 这里给出传送门 回调函数之美 然而如果业务逻辑过多时, 回调函数会产生深层嵌套, 对程序员极不友好,
    如下代码所示有一个业务逻辑, 需要对a, b, c三个文件一次读取

        var fs = require('fs');fs.readFile('./a.txt', function(err1, data1) {fs.readFile('./b.txt', function(err2, data2) {fs.writeFile('./ab.txt', data1 + data2, function(err) {console.log('read and write done!');});});});

    三个异步函数嵌套看起来挺简单的, 这里知识简单假设, 抛砖引玉, 如果有5个,10个甚至更多的异步函数要顺序执行,那要嵌套(大家都不喜欢身材横着长吧哈哈)说实话相当恐怖,代码会变得异常难读,难调试,难维护。这就是所谓的回调地狱或者callback hell。正是为了解决这个问题,才有了后面两节要讲的内容,用promise或generator进行异步流程管理。异步流程管理说白了就是为了解决回调地狱的问题。所以说任何事情都有两面性,异步编程有它独特的优势,却也同时遇到了同步编程根本不会有的代码组织难题。

  • 事件监听(事件发布/订阅)
    事件监听模式是一种广泛应用于异步编程的模式, 是回调函数的事件化,即发布/订阅模式,

        var util = require('util');var events = require('events');function Stream() {events.EventEmitter.call(this);}util.inherits(Stream, events.EventEmitter)let got = new Stream();got.on("done", function (params) {console.log(params);});got.on("done", function (params) {console.log('QWER');});got.emit("done", 'diyige');console.log('-----------------');var emitter = new events.EventEmitter();emitter.on("done", function (params) {console.log(params);});emitter.on("done", function (params) {console.log('ZXCV');});emitter.emit("done", 'dierge');// diyige// QWER// dierge// ZXCV
  • Promise对象
    Promise 是异步编程的一种解决方案,它是比传统的解决方案——回调函数和事件——更合理和更强大, 它的目的是替换以前回调函数的比不编程方案, 也是后续介绍的异步解决方案的基础, 它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象, 现在的 js库几乎都支持这种异步方案

    promise对象有以下特点

    • 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变
    • 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

图片描述

下面为单个promise对象应用方法
    var promise = new Promise(function(resolve,reject){// ... some codeif(/* 异步操作成功 */){resolve(value);}else{reject(error);}});

通常用promise 的时候我们一般把它相应的业务包装起来下图所示模拟了一个读取文件的异步
promise 函数,

    var readFile =  function (params) {return new Promise(function(resolve, reject){setTimeout(function(){resolve(params);}, 2000);});}readFile('file1').then(function (data) {console.log(data);return readFile('file2')}).then(function (data) {console.log(data);return readFile('file3')}).then(function (data) {console.log(data);return readFile('file4')}).then(function (data) {console.log(data);return readFile('file5')}).then(function (data) {console.log(data);})//file1//file2//file3//file4//file5
  • 流程控制库
    还有一种需要手工调用采能够处理后续任务的, 在这里只简单介绍一种, 我们称之为尾触发, 常用的关键字为 next , 为什么要讲到它是因为它是 node 神级框架 express中采用的模式, 这里可能要涉及一些后端node的内容
    在 node 搭建服务器时需要面向 切面编程 ,这就需要各种各样的中间件

        var app = connect();// Middlewareapp.use(connect.staticCache());app.use(connect.static(__dirname + '/public'));app.use(connect.cookieParser());app.use(connect.session());app.use(connect.query());app.use(connect.bodyParser());app.use(connect.csrf());app.listen(3001);

    在通过 use() 方法监听好一系列中间件后, 监听端口上的请求, 中间件采用的是尾触发的机制, 下面是个一个简单的中间件

        function (req, res, next) {// express中间件}

    每个中间件传递请求对象, 响应对象, 和尾触发函数, 通过队列形成一个处理流, 如下图
    图片描述
    中间件机制使得在处理网络请求时, 可以像面向切面编程一样进行过滤, 验证, 日志等功能.

ES 6:

  • Generator函数(协程coroutine)
    Generator 函数有多种理解角度。语法上,Generator 函数是一个状态机,封装了多个内部状态。
    执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。执行函数后返回的是一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

        function* helloWorldGenerator() {yield 'hello';yield 'world';return 'ending';}var hw = helloWorldGenerator();hw.next()// { value: 'hello', done: false }hw.next()// { value: 'world', done: false }hw.next()// { value: 'ending', done: true }hw.next()// { value: undefined, done: true }

    下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

  • 基于 Promise 对象的自动执行
    generater/yield函数还无法真正解决异步方案的问题, 需要配合额外的执行模块 如 TJ Holowaychuk 的 co 模块, 在这里用promise模块进行generater函数的自动执行;

        var fs = require('fs');var readFile = function (fileName){return new Promise(function (resolve, reject){fs.readFile(fileName, function(error, data){if (error) return reject(error);resolve(data);});});};var gen = function* (){var f1 = yield readFile('/etc/fstab');var f2 = yield readFile('/etc/shells');console.log(f1.toString());console.log(f2.toString());};
    /*****************************************var g = gen();g.next().value.then(function(data){g.next(data).value.then(function(data){g.next(data);});});
    *****************************************/// 自动执行函数        function run(gen){var g = gen();function next(data){var result = g.next(data);if (result.done) return result.value;result.value.then(function(data){next(data);});}next();}run(gen);   

ES 7:

  • async/await
    终于来到了我们梦寐以求的的"终极"异步解决方案, 或许你有些失望, 当然这种失望是async/await 仅仅是语法糖, async/await 就是 generater/yield/promise + 自动执行模块的封装.相对于前辈 async 函数可以自动执行 并且 await 关键字后面则只能带promise队形--这里注意 await 后面支持其他数据类型, 但是底层也会将其转化为promise对象

    async函数对 Generator 函数的改进,体现在以下四点。

    • 内置执行器。
      Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器,这完全不像 Generator 函数,需要调用next方法,或者用co模块,才能真正执行,得到最后结果。
    • 更好的语义。
      async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
    • 更广的适用性。
      co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)
    • 返回值是 Promise。
      async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。

          function name(params) {return new Promise(function (resolve, reject) {setTimeout(() => {resolve(params)}, 3000);});}async function myf () {let gf = await name('xiaohua');let gf2 = await name('xiaohong');return gf + gf2 }async function myf3 (params) {let aaa = await myf();return aaa;}myf3().then(function (params) {console.log(params);});// xiaohuaxiaohong
async/await 对前者的generater/yield 进行了高度的封装配合那些支持 promise 实现的库可以完美的像普通函数一样调用, 并且async函数与其他async函数也可以完美无缝连接, 堪称终极方案

koa2已经支持 async/await 但是最新的 express框架依然没有支持这种写法, async/await 是大势所趋, 或许不久的将来 express也会支持它, 我们拭目以待

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/452766.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

sql注入基本原理

1. 参考文献: 趣解SQL注入原理 Sql注入基本原理 2.参考书籍

centos7硬盘分区

首先在虚拟机的设置中为系统添加硬盘 使用fdisk -l /dev/sdb 查看未分区的硬盘 fdisk -l /dev/sda 这是已经分区好得 接下来我们就要对sdb进行分区: 首先使用fdisk /dev/sdb 接着输入m可以看到详细命令 进行添加分区 已经建立好4个主分区,在建立时会看到以下 删除…

从MapReduce的执行来看如何优化MaxCompute(原ODPS) SQL

摘要: SQL基础有这些操作(按照执行顺序来排列): from join(left join, right join, inner join, outer join ,semi join) where group by select sum distinct count order by 如果我们能理解mapreduce是怎么实现这些SQL中的基本操…

套接字(socket)基本知识与工作原理

套接字(socket)基本知识与工作原理 一、Socket相关概念 Socket通常也称作“套接字”,用于描述IP地址和端口,是一个通信链的句柄。(其实就是两个程序通信用的。) SOCKET用于在两个基于TCP/IP协议的应用程序之…

python 多线程--重点知识

1.全局变量global的用法 2.多线程共享全局变量-args参数 注意args参数类型为元组,逗号不能少!

当导用模块与包的import与from的问题(模块与包的调用)

当在views.py里写impor models会不会报错呢? 1、Python里面的py文件都是每一行的代码。2、Python解释器去找一个模块的时候,只去sys.path的路径里找3、django项目启动(django项目的启动文件是manage.py)启动项目是将manage.py的路…

Python多线程--互斥锁、死锁

1、互斥锁 为解决资源抢夺问题,使用mutex Threading.Lock()创建锁,使用mutex.acquire()锁定,使用mutex.release()释放锁。 代码一: import threading import time# 定义一个全局变量 g_num 0def test1(num):global g_num# 上锁…

freemind 要下载java_Freemind

动手编辑先按Ctrln,新建一个文件。这时出现了一个根节点。用光标单击它,改成“我学FreeMind”,然后在节点之外任一地方点击鼠标(或按Enter)完成编辑。然后,按Insert键,输入“下载安装”,按Enter键&#xff…

pyecharts对于经纬度_一文带你掌握Pyecharts地理数据可视化的方法

本文主要介绍了Pyecharts地理数据可视化,分享给大家,具体如下:一、Pyecharts简介和安装1. 简介Echarts 是一个由百度开源的数据可视化,凭借着良好的交互性,精巧的图表设计,得到了众多开发者的认可。而 Pyth…

使用Sqlmap对dvwa进行sql注入测试(初级阶段)

0.测试准备 1)打开Kali虚拟机终端; 2)打开靶机OWASP,并通过浏览器,输入IP地址进入dvwa的主页,然后选择SQL injection进入SQL注入的测试页面 1.获取DVWA的url和cookie 在输入框中输入1,显示有内容&…

如果备份还原SecureCRT、Xshell远程工具远程

因为有时候电脑操作系统要重新安装,需要将远程备份下来。或者要将远程发给其他同事。一、如何备份还原SecureCRT远程1、打开options-global options---general---configuration paths找到配置文件保存路径,如下图:2、打开C:\Users\NUC\AppDat…

Centos7 下yum安装mysql

转载于:https://www.cnblogs.com/nbjjy/p/9023991.html

Python协程--实现斐波那契数列(Fibonacci)的几种方式

1.使用for遍历list数组 # 使用for遍历list数组 nums list() a 0 b 1 i 0while i < 10:nums.append(a)a, b b, abi 1for num in nums:print(num)2.使用迭代器完成 class Fibonacci(object):def __init__(self, all_num):self.all_num all_numself.current_num 0sel…

FTP服务的简介和配置详解

FTP服务的简介和配置详解注意&#xff1a;配置FTP服务时&#xff0c;最好关闭防火墙和selinux1、FTP服务简介FTP 是File Transfer Protocol&#xff08;文件传输协议&#xff09;的英文简称&#xff0c;而中文简称为“文件传输协议”。用于Internet上的控制文件的双向传输。同时…

Python协程--生成器(实现多任务)

0.生成器 1.使用yield完成多任务 import timedef task_1():while True:print("---1----")time.sleep(0.1)yielddef task_2():while True:print("---2----")time.sleep(0.1)yielddef main():t1 task_1()t2 task_2()# 先让t1运行一会&#xff0c;当t1中遇…

技术分享连载(六十一)

资源管理 Q1&#xff1a;Unity5.4.1中&#xff0c;我将需要的Shader打到一个AssetBundle包中&#xff08;包含一个关联了所有Shader的Shader Variants&#xff09;&#xff0c;分别用Shader.WarmupAllShaders和ShaderVariantCollection.WarmUp两种方式进行预加载&#xff0c;后…

SNF软件开发机器人-子系统-导出-导入功能-多人合作时这个功能经常用到

导出 导出可以将资源表和子系统导出并形成一个json文件。 1.效果展示&#xff1a; 2.使用说明&#xff1a; 点击导出按钮后会弹出一个导出页面。页面的左侧可以选择功能&#xff0c;右侧可以选择资源表&#xff0c;选择功能的同时右侧中功能所需的资源表也会被选择。当功能之间…

基于物理的渲染-用真实的环境光照亮物体

目前&#xff0c;在游戏引擎中用于照亮物体的光源非常丰富。其中&#xff0c;比较常用的有&#xff1a;平行方向光、点光源、聚光灯以及体积光等&#xff0c;但它们都是对真实光源的近似&#xff0c;并不能很好地模拟真实世界中的复杂光照情况。为了增加光照效果的真实感&#…

克隆CentOS6虚拟机eth0被修改为eth1如何修改eth0

2019独角兽企业重金招聘Python工程师标准>>> 直接修改 /etc/sysconfig/network-script/ifcfg-eth0 删掉UUID HWADDR 配置静态地址 然后&#xff1a; rm -rf  /etc/udev/rules.d/70-persistent-net.rules然后reboot 转载于:https://my.oschina.net/hengbao666/blog/…