Javascript 中通过 yield 和 promise 使异步变同步


背景

由于最近一段时间一直在用react-native进行APP的开发,所以接触了不少 javascript。

在我们第一次使用react-native + redux + saga开发的过程中,学习、见识到了不少javascript神奇的功能,比如在使用saga的过程中用到了 yield,并且对于其使得异步操作同步化十分好奇,就进行了一番探索。

yield简单介绍

先看一段简单的代码

JavaScript 全选
function* gen() {
  yield console.log(1)
  yield console.log(2)
  console.log(3)
}

const g = gen()
g.next()
g.next()
g.next()

输出如下

JavaScript 全选
1
2
3

函数gen的声明使用了function*,使得gen函数成为一个generator,并且可以在其中里面使用yield关键字,gen()返回一个generator对象,通过next()依次调用。

在我的理解看来,可以将yield关键字理解为函数的断点,每次next()就会从上次的断点(yield)执行到下次的断点(yield),直到函数结束退出,于是就产生了上面的结果。

next() 的返回值

修改一下上面程序的输出代码,用来查看一下next()函数的返回值

JavaScript 全选
const g = gen()
console.log(g.next())
console.log(g.next())
console.log(g.next())

输出:

JavaScript 全选
1
{ value: undefined, done: false }
2
{ value: undefined, done: false }
3
{ value: undefined, done: true }

可以看到,next()函数的返回值是一个{value: any, done: boolean}的object,value是运行到这个断点的返回值,done表示该函数是否已经执行完毕,对next()方法返回值的了解,会有助于我们下面的程序实现。

promise 的简单了解

promise的出现,是为了解决javascript中的异步无限回调,一般的使用方法如下:

JavaScript 全选
function delay(ms) {  
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms);
  });
}

console.log(1)
delay(300).then(() => console.log(2))
console.log(3)

输出:

JavaScript 全选
1
3
2

在delay()函数中使用setTimeout()来模拟一个异步操作,再用promise封装一下,使得可以使用promise的then()方法来在异步操作完成之后,执行特定的代码。

现在promise的使用已经很普遍,javascript标准中的fetch()函数也是支持promise的回调机制,以方便开发者更容易的处理网络请求的异步返回。

异步操作 - 问题的产生

在我们的使用过程中,有类似如下结构的代码:

JavaScript 全选
function* baum() {
  yield delay(300).then(() => console.log(1))
  yield console.log(2)
  yield delay(300).then(() => console.log(3))
  yield console.log(4)
}

const b = baum()
b.next()
b.next()
b.next()
b.next()

输出如下:

JavaScript 全选
2
4
1
3

函数baum()结构表达的意思是,有一些同步的操作,然后会发出异步的请求(比如网络请求),异步请求结束后,再执行后面的代码。但是因为delay()函数的异步使得1和3的输出延迟了,并没有达到预期效果。

可是令人十分费解的是,在saga中,这样的程序结构,是会按照顺序执行的效果呈现出来,即输出是1,2,3,4,所以一定是saga在对诸如baum()这样的generator进行了一层包裹,使得里面的同步操作可以等待上一个异步promise函数执行完成后再被触发。

异步变同步 - 机械的实现

为了可以使得代码的执行顺序变成函数里面的书写顺序,我们先看一下baum()函数每次next()的返回值都是什么,对上一节的输出代码稍作改写:

JavaScript 全选
const b = baum()
console.log(b.next())

输出:

JavaScript 全选
{ value: Promise { <pending> }, done: false }
1

上面第一行输出中的value值,是delay(300).then(() => console.log(1))这段代码的执行结果,那么我们可知,对于一个promise,它的then()函数的返回值同样是一个promise对象。由此来说,只要将同步的next()执行,放到它前面异步的promise中的then()函数里,即可以达到同步代码发生在异步代码操作之后的效果了。

 

说起来有点绕,看一下改进之后的代码:

JavaScript 全选
const b = baum()
b.next().value.then(() => {
  b.next()
})

输出:

JavaScript 全选
1
2
3
4

现在已经通过promise的then()方法,做到了异步、同步代码执行时的所见即所得,即程序的输出顺序,是和书写顺序一致的。

那么最后的任务,就是对上面的代码进行封装,以免去这种手工机械化的调用。

异步变同步 - 自动化的实现

经过一番折腾,最后写出了下面一个函数himmel()来使得generator中的调用,无论异步的还是同步的yield操作,都是依照着代码的书写顺序执行的:

JavaScript 全选
function himmel(gen) {
  const item = gen.next()
  if (item.done) {
    return item.value
  }

  const { value, done } = item
  if (value instanceof Promise) {
    value.then((e) => himmel(gen))
  } else {
    himmel(gen)
  }
}

函数的实现是一个递归,接收参数是一个generator实例,退出条件即为当yield结果中的done为true的时候。后面的代码会判断value是否是一个promise,如果是的话,就在then()方法中递归,否则就认为是同步代码,直接递归。

测试一下:

JavaScript 全选
himmel(baum())

输出:

JavaScript 全选
1
2
3
4

结果也是正确的。

验证以上思路的可行性

在yield出现的时候,就随之出现了一个比较有名的库叫作 co ,这个库的作用就是控制同步与异步代码的执行顺序,在它的说明中原文是 generator based flow-control。

看过co的实现代码之后,发现其本质上也是这么实现的。只不过那个库加上了更多的边界检测代码,做的更加健壮。

至此,就是我对于在javascript中使用yield+promise,从而对同步、异步代码进行流程控制的思考与总结。

 

 

原文链接:https://www.jianshu.com/p/c1b8b89c4905

 

 

 

 

 

 

 

 

版权声明:本文为YES开发框架网发布内容,转载请附上原文出处连接
张国生
上一篇:深入理解js中的yield
下一篇:浏览器限制最小字体为12号的解决办法
评论列表

发表评论

评论内容
昵称:
关联文章

Javascript 通过 yield promise 使异步同步
C# 从做早餐看同步异步
.net异步Task转同步
javascriptletvar的区别
深入理解jsyield
vue异步函数asyncawait的用法
化繁为简,用几个例子介绍JavaScript异步处理async awite
Task 使用详细[基础操作,异步原则,异步函数,异步模式]
C# - 逆的具体应用场景
C# 使用JavaScript给PDF文档设置过期时间
javascript删除html字符串的空行
ts无法使用js括号[]方式通过属性名获得对象属性值的解决办法
混淆工具JavaScript obfuscator中文帮助文档
vscode同步配置时,重新生成 github token 之后,怎样继续下载配置
C# 执行Javascript脚本
html img标签更改图片尺寸后图片得模糊
javascript表单Form转json进行ajax提交Jquery表单json提交
HttpContext.Current:异步模式下的疑似陷阱之源
在Winform项目Web API的.NetCore项目使用Serilog 来记录日志信息
ABP VNext框架Winform终端的开发客户端授权信息的处理

联系我们
联系电话:15090125178(微信同号)
电子邮箱:garson_zhang@163.com
站长微信二维码
微信二维码