JS异步编程 (2) - Promise、Generator、async/await

上篇文章我们讲了下JS异步编程的相关知识,比如什么是异步,为什么要使用异步编程以及在浏览器中JS如何实现异步的。
最后我们捎带讲了几种JS异步编程模式(回调,事件和发布/订阅模式),这篇我们继续去深入了解下其他的几种异步编程模式。

Promise

Promise是ES6推出的一种异步编程的解决方案。其实在ES6之前,很多异步的工具库就已经实现了各种类似的解决方案,而ES6将其写进了语言标准,统一了用法。Promise解决了回调等解决方案嵌套的问题并且使代码更加易读,有种在写同步方法的既视感。

我们先来简单了解下ES6中Promise的用法


var p = new Promise(function async(resolve, reject){
// 这里是你的异步操作
setTimeout(function(){
if(true){
resolve(val);
}else{
reject(error);
}
}, 1000)
}) p.then(function(val){
console.log('resolve');
}, function(){
console.log('reject');
})

首先,ES6规定Promise是个构造函数,其接受一个函数作为参数如上面代码中的async函数,此函数有两个参数,resolve、reject分别对应成功失败两种状态,我们可以选择在不同时候执行resolve或者reject去触发下一个动作,执行then方法里的函数。

我们可以简单对比下回调的写法和promise的写法的不同

对于传统回调写法来说,一般会写成这样

asyncFn1(function () {
asyncFn2(function() {
asyncFn3(function() {
// xxxxx
});
});
});

或者我们将各个回调函数拆出来独立来写以减少耦合,像是这样:

function asyncFn1(callback) {
return function() {
console.log('asyncFn1 run');
setTimeout(function(){
callback();
}, 1000);
}
} function asyncFn2(callback) {
return function(){
console.log('asyncFn2 run');
setTimeout(function(){
callback();
}, 1000);
}
} function normalFn3() {
console.log('normalFn3 run');
} asyncFn1(asyncFn2(normalFn3))()

最后我们看下Promise的写法

function asyncFn1() {
console.log('asyncFn1 run');
return new Promise(function(resolve, reject) {
setTimeout(function(){
resolve();
}, 1000)
})
} function asyncFn2() {
console.log('asyncFn2 run');
return new Promise(function(resolve, reject) {
setTimeout(function(){
resolve();
}, 1000)
})
} function normalFn3() {
console.log('normalFn3 run');
} asyncFn1().then(asyncFn2).then(normalFn3);

这样来看无论是第一种还是第二种写法,都会让人感到不是很直观,而Promise的写法更加直观和语义化。

Generator

Generator函数也是ES6提供的一种特殊的函数,其语法行为与传统函数完全不同。

我们先直观看个Generator实际的用法

function* oneGenerator() {
yield 'Learn';
yield 'In';
return 'Pro';
} var g = oneGenerator(); g.next(); // {value: "Learn", done: false}
g.next(); // {value: "In", done: false}
g.next(); // {value: "Pro", done: true}

Generator函数是一种特殊的函数,他有这么几个特点:

  • 声明时需要在function后面加上*,并且配合函数里面yield关键字来使用。

  • 在执行Generator函数的时候,其会返回一个Iterator遍历器对象,通过其next方法,将Generator函数体内的代码以yield为界分步执行

  • 具体来说当执行Generator函数时,函数并不会执行,而是需要调用Iterator遍历器对象的next方法,这时程序才会执行从头或者上一个yield之后到下一个yield或者return或者函数体尾部之间的代码,并且将yield后面的值,包装成json对象返回。就像上面的例子中的{value: xxx, done: xxx}

  • value取的yield或者return后面的值,否则就是undefined,done的值如果碰到return或者执行完成则返回true,否则返回false。

我们知道了简单的Generator函数的用法以后,我们来看下如何使用Generator函数进行异步编程。

首先我们先来看下使用Generator函数能达到怎样的效果。


// 使用Generator函数进行异步编程
function* oneGenerator() {
yield asyncFn1();
yield asyncFn2();
yield normalFn3();
} // 我们来对比一下Promise
asyncFn1().then(asyncFn2).then(normalFn3);

我们可以看出使用Generator函数进行异步编程更像是在写同步任务,对比Promise少了很多次then方法的调用。

好,那么接下来我们就来看下如何实际使用Generator函数进行异步编程。

这里我要特别说明一下,事实上Generator函数不像Promise一样是专门用来解决异步处理而产生的,人们只是使用其特性来产出了一套异步的解决方案,所以使用Generator并不像使用Promise一样有一种开箱即用的感觉。其更像是在Promise或者回调这类的解决方案之上又封装了一层,让你可以像上面例子里一样去那么写。

我们还是具体来看下上面的例子,我们知道单写一个Generator是不能运行的对吧,我们需要执行他并且使用next方法来让他分步执行,那么什么时候去调用next呢?答案就是我们需要在异步完成时去调用next。我们来按照这个思路补全上面的例子。

var g;

function asyncFn() {
setTimeout(function(){
g.next();
}, 1000)
} function normalFn() {
console.log('normalFn run');
} function* oneGenerator() {
yield asyncFn();
return normalFn();
} g = oneGenerator(); g.next(); // 这里在我调用next方法的时候执行了asyncFn函数
// 然后我们的希望是在异步完成时自动去再调用g.next()来进行下面的操作,所以我们必须在上面asyncFn函数体内的写上g.next(); 这样才能正常运行。 // 但其实这样是比较奇怪的,因为当我定义asyncFn的时候其实是不知道oneGenerator执行后叫什么名儿的,即使我们提前约定叫g,但这样asyncFn就太过于耦合了,不仅写法很奇怪而且耦合太大不利于扩展和重用。反正总而言之这种写法很不好。

那么怎么解决呢,我们需要自己写个方法,能自动运行Generator函数,这种方法很简单在社区里有很多,最著名的就是大神TJ写的co模块,有兴趣的同学可以看下其源码实现。这里我们简单造个轮子:

// 如果我们想要去在异步执行完成时自动调用next就需要有一个钩子,回调函数的callback或者Promise的then。

function autoGenerator(generator){
var g = generator(); function next(){
var res = g.next(); // {value: xxx, done: xxx} if (res.done) {
return res.value;
} if(typeof res.value === 'function'){ // 认为是回调
res.value(next);
}else if(typeof res.value === 'object' && typeof res.value.then === 'function'){ // 认为是promise
res.value.then(function(){
next();
})
}else{
next();
}
} next();
} // ----
function asyncFn1(){
console.log('asyncFn1');
return new Promise(function(resolve){
setTimeout(function(){
resolve();
}, 1000)
})
} function asyncFn2() {
console.log('asyncFn2');
return function(callback){
setTimeout(function(){
callback();
}, 1000);
}
} function normalFn() {
console.log('normalFn');
} function* oneGenerator() {
yield asyncFn1();
yield asyncFn2();
yield normalFn();
} autoGenerator(oneGenerator);

这个方法我们简单实现了最核心的部分,有些判断可能并不严谨,但大家理解这个思路就可以了。有了这个方法,我们才可以方便的使用Generator函数进行异步编程。

Async/Await

如果你学会了Generator函数,对于Async函数就会很容易上手。你可以简单把Async函数理解成就是Generator函数+执行器。我们就直接上实例好了

function asyncFn1(){
console.log('asyncFn1');
return new Promise(function(resolve){
setTimeout(function(){
resolve('123');
}, 2000)
})
} function asyncFn2() {
console.log('asyncFn2');
return new Promise(function(resolve){
setTimeout(function(){
resolve('456');
}, 2000)
})
} async function asyncFn () {
var a = await asyncFn1();
var b = await asyncFn2(); console.log(a,b)
} asyncFn(); // asyncFn1
// asyncFn2
// 123,456

当然async里实现的执行器肯定是跟咱们上面简单实现的有所不同,所以在用法上也会有些注意的点

  • 首先async函数的返回值是一个Promise对象,不像是generator函数返回的是Iterator遍历器对象,所以async函数执行后可以继续使用then等方法来继续进行下面的逻辑

  • await后面一般跟Promise对象,async函数执行时,遇到await后,等待后面的Promise对象的状态从pending变成resolve的后,将resolve的参数返回并自动往下执行直到下一个await或者结束

  • await后面也可以跟一个async函数进行嵌套使用。

对于异步来说,还有很多的知识点我们没有讲到,比如异常处理,多异步并行执行等等,这篇和上篇文章主要还是希望大家对异步编程有个直观的了解,清楚各种解决方案之间的区别和优劣。由于篇幅和精力有限,对于其他我们没讲到的知识点,如果大家有兴趣有机会我会再写文章深入讲解的。

另外就是如果你在学习前端的过程中有任何问题想要咨询,欢迎关注 LearnInPro的公众号,在上面随时向我提问哦。

JS异步编程 (2) - Promise、Generator、async/await的更多相关文章

  1. js异步编程终级解决方案 async/await

      在最新的ES7(ES2017)中提出的前端异步特性:async.await. async.await是什么 async顾名思义是“异步”的意思,async用于声明一个函数是异步的.而await从字 ...

  2. Promise, Generator, async/await的渐进理解

    作为前端开发者的伙伴们,肯定对Promise,Generator,async/await非常熟悉不过了.Promise绝对是烂记于心,而async/await却让使大伙们感觉到爽(原来异步可以这么简单 ...

  3. 【C# TAP 异步编程】三、async\await的运作机理详解

    [原创] 本文只是个人笔记,很多错误,欢迎指出. 环境:vs2022  .net6.0 C#10 参考:https://blog.csdn.net/brook_shi/article/details/ ...

  4. C#进阶——从应用上理解异步编程的作用(async / await)

    欢迎来到学习摆脱又加深内卷篇 下面是学习异步编程的应用 1.首先,我们建一个winfrom的项目,界面如下: 2.然后先写一个耗时函数: /// <summary> /// 耗时工作 // ...

  5. 深入理解JS异步编程三(promise)

    jQuery 原本写一个小动画我们可能是这样的 $('.animateEle').animate({ opacity:'.5' }, 4000,function(){ $('.animateEle2' ...

  6. JS异步编程 (1)

    JS异步编程 (1) 1.1 什么叫异步 异步(async)是相对于同步(sync)而言的,很好理解. 同步就是一件事一件事的执行.只有前一个任务执行完毕,才能执行后一个任务.而异步比如: setTi ...

  7. 理解js异步编程

    Promise 背景 javascript语言的一大特点就是单线程,在某个特定的时刻只有特定的代码能够被执行,并阻塞其它的代码,也就是说,同一个时间只能做一件事. 怎么做到异步编程?回调函数.直到no ...

  8. 一个例子读懂 JS 异步编程: Callback / Promise / Generator / Async

    JS异步编程实践理解 回顾JS异步编程方法的发展,主要有以下几种方式: Callback Promise Generator Async 需求 显示购物车商品列表的页面,用户可以勾选想要删除商品(单选 ...

  9. callback vs async.js vs promise vs async / await

    需求: A.依次读取 A|B|C 三个文件,如果有失败,则立即终止. B.同时读取 A|B|C 三个文件,如果有失败,则立即终止. 一.callback 需求A: let read = functio ...

随机推荐

  1. phpmyadmin数据表结构没有显示注释列

    新开的一个项目,用phpmyadmin作为图形化操作数据库工具.创建数据表时为其每列添加好注释,浏览数据表内容有显示注释内容,但是查看数据表结构没有显示注释列,不方便直观查看数据表每列的意思. 上网搜 ...

  2. SPOJ QTREE7

    题意 一棵树,每个点初始有个点权和颜色 \(0 \ u\) :询问所有\(u,v\) 路径上的最大点权,要满足\(u,v\) 路径上所有点的颜色都相同 $1  u \(:反转\)u$ 的颜色 \(2 ...

  3. Makefile一 头文件及库搜索路径

    头文件及库搜索路径 头文件的搜索路径: 头文件的搜索规则是:找到就使用,停止继续往下寻找 1: #include “mytest.h” 搜索的顺序为: (1)先搜索当前目录 (2)然后搜索编译时 -I ...

  4. 【Python】Sublime text 3 搭建Python IDE

    背景: 最经遇到一件很苦恼的事情,就是在Sublime text 3中写的Python代码直接挪到python原生的ide中老是报格式的错误(有时让人讨厌的缩进),没有办法,看到Sublime tex ...

  5. QT 定时执行某个函数,隐藏某个控件

    QTimer::singleShot(3000, this, SLOT(slotHideFinishedLabel())); // 这里是一个3秒定时器, 且只执行一次. #include " ...

  6. Ganglia安装

    一.rrdtool安装 1.1 安装依赖包 由于rrdtool依赖的包比较多,而且包之间也存在依赖,故使用yum安装由于服务器无法联网,故使用iso文件创建本地yum源,方法见下: (1)创建iso存 ...

  7. 推卡:“积分侠”的福利 广发DIY信用卡

    广发diy信用卡最大的优势在持卡人在三大类商户刷卡消费可享受3倍积分优惠,很多卡友不知道这些商户到底有哪些,以及商户mcc码是什么,下面和小编一起来看看. 可享受3倍积分的商户类型 持卡人可在以下三大 ...

  8. pt-heartbeat(percona toolkit)

    pt-heartbeat是用来监控主从延迟的一款percona工具,现在我们大部分的MySQL架构还是基于主从复制,例如MHA,MMM,keepalived等解决方案.而主从环境的话,我们很关心的就是 ...

  9. linux下redis4.0.2安装与部署

    一.redis的介绍 Redis是当前比较热门的NOSQL系统之一,它是一个key-value存储系统.和Memcache类似,但很大程度补偿了Memcache的不足,它支持存储的value类型相对更 ...

  10. 「C语言」数据类型及混合运算与类型转换

    深入学习C语言时,有必要先了解一下数据类型的概念,以及它们之间的混合运算与类型转换. 本篇文章便是根据<C语言程序设计教程>和在线翻阅资料后整理而出.(练习题将逐步更新) 目录:     ...