javascript 函数的暂停和恢复实例详解


Posted in Javascript onApril 25, 2020

本文实例讲述了javascript 函数的暂停和恢复。分享给大家供大家参考,具体如下:

javascript 异步编程从来都是一个难题,最开始我们用 callback,但随之触发了回调地狱,于是“发明” Promise 解决 callback 嵌套过深的问题。然而由于滥用 Promise(一连串的 then),代码变得不易阅读了。此时,async-await 横空出世,它让我们可以用同步的方式编写异步代码,简直 amazing,以至于有人说它就是 javascript 异步编程的银弹。

P.S.代码只是演示,并不可用

function getProfile(id) {
 return window.fetch(`https://api.com/wedding/profile/${weddingId}`
}

async function getWeddingDetail(weddingId) {
 try {
  // 暂停执行
  const wedding = await window.fetch(`https://api.com/wedding/${weddingId}`);
  // 当结果返回恢复执行,接着继续暂停
  const groom = await getProfile(wedding.groomId);
  // ... 恢复执行 -> 暂停 ...
  const bride = await getProfile(wedding.brideId);
  // ... 恢复执行
  return { wedding, bride, groom };
 } catch (error) {
  handleError(error);
 }
}

没有银弹

然而计算机科学领域中并不存在银弹。async-await 也有缺点,比如你忘了写 await,不信?

假设别人编写了一个工具函数叫getProfile,如果不了解它的具体实现,你是不是就把它当做同步函数,即便getProfile是异步的。

当然,这只是一个小问题,更让我难受的是,如果你在一个函数中使用了 async,那么调用它的函数也得变成一个 async,若还有另一个函数要调用这个调用函数......holly shit!现在你明白了吧。

有没有两全其美的办法?

// getWeddingDetail 根本不用关心内部的函数是异步or同步
function getWeddingDetail(weddingId) {
 const wedding = window.fetch(`https://api.com/wedding/${weddingId}`);
 const groom = getProfile(wedding.groomId);
 const bride = getProfile(wedding.brideId);
 return { wedding, bride, groom };
}

没有什么是一个中间层解决不了的

异步编程的核心,就是函数暂停和恢复执行。而决定一个函数是暂停还是恢复执行,这是 js 运行时干的活儿,难不成我们今天要深入引擎实现?

No!我不了解 C++,也不懂 js 引擎是如何实现的。

但是呢,我可以写一个中间层(函数runtime),尝试实现上面的需求,当然,这会有一些限制。

一、入口函数

假设要运行的函数如下:

function main() {
 const id = 123;
 console.log('Getting wedding:', id);

 const { wedding, bride, groom } = getWeddingDetail(id);

 console.log('Wedding detail:', wedding);
}

我们期望能够按照下面的方式运行:

function runtime(mainFn) {
 mainFn();
}

// start runtime
runtime(main);

基本框架已经有了,接着干啥?

首先,要搞清楚在不用 await 的前提下,如何中断函数运行。

然后,在合适的地方恢复执行。

js 中有两种方法中断函数运行:return和throw。我选择 throw,因为它表示遭遇异常导致的中断。好了,我们改造一下 runtime

function runtime(mainFn) {
 const _originalFetch = window.fetch;
 window.fetch = (url, options) => {
  // "暂停"
  throw new Error();
 };
 // 运行入口函数
 runMain();

 function runMain() {
  try {
   mainFn();
  } catch (error) {
   // 函数 "暂停"
   // 恢复并重新执行 mainFn
   runMain();
  }
 }
}

先忽略这段代码出现的问题,把目光聚集在函数“中断”“恢复”这两个点上,显然,目的已经达到。接下来对它进行优化。

首当其冲的是 runMain,只需要当 window.fetch 成功后再执行:

function runtime(mainFn) {
 const _originalFetch = window.fetch
 window.fetch = (url, options) => {
  _originalFetch(url, options).then(res => {
   // 返回结果后恢复执行
   runMain()
  })
  throw new Error()
 }

 runMain();

 function runMain() {
  try {
   mainFn();
  } catch (error) {
   // ignore
  }
 }
}

window.fetch 每次抛出异常,这导致 mainFn 无限循环的执行。

要解决这个问题,需要引入缓存,使得我们仅需要在第一次 fetch 时抛出异常,而为后面的请求返回响应。

function runtime(mainFn) {
 const _originalFetch = window.fetch
 windo.fetch = (url, options) => {
  if (cache.has([url, options])) return cache.get([url, options])

  _originalFetch(url, options).then(res => {
   cache.set([url, options], res)
   runMain()
  })

  throw new Error()
 }

 runMain();

 function runMain() {
  try {
   mainFn();
  } catch (error) {
   // ignore
  }
 }
}

成功啦!

运行程序,检查 console 的输出,由于重复运行了多次,'Getting wedding:', 123也被显示了多次,这是 console.log 的副作用导致的。

二、纯函数

runtime 只允许运行纯函数,如果你的代码中有副作用,则必须添加限制条件:runSideEffects().

function main() {
 const id = 123;
 runSideEffects(() => console.log('Getting wedding:', id));

 const { wedding, bride, groom } = getWeddingDetail(id);

 runSideEffects(() => console.log('Wedding detail:', wedding));
}

sideEffects 的实现非常容易:

function runtime(mainFn) {
 // 参考上面的代码

 // 提供 `runSideEffects`
 const sideEffects = [];
 window.runSideEffects = fn => {
  sideEffects.push(fn);
 };

 runMain();

 function runMain() {
  try {
   mainFn();
   sideEffects.forEach(fn => fn());
  } catch (error) {
   // 清除副作用
   sideEffects.splice(0, sideEffects.length);
  }
 }
 
}

再次运行,'Getting wedding:', 123只显示一次啦~

到底干了些啥?

为了模仿函数暂停和恢复,我们通过 throw 一个错误来“暂停”函数,重新运行来“恢复”函数。

为了从暂停处“恢复”,需要将抛出的错误替换成函数返回值,我们用缓存机制达到了这个目的。

最后,为了能安全的重复执行函数,需要将它转化为一个纯函数。如果有副作用,则将它们收集起来,在函数运行成功后,再执行副作用。

扯这么多,有什么实际用途?

本文的灵感来自于React Suspense。有了 Suspense,就可以像下面这样来获取数据:

function Component() {
 const data = getDataFromNetwork();
 return <div />;
}

getDataFromNetwork 将发起异步请求,所以它是一个异步函数,但 React 让它看来是是一个同步操作。这很有趣~

原文阅读:pause-and-resume-a-javascript-function

感兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:http://tools.3water.com/code/HtmlJsRun测试上述代码运行效果。

更多关于JavaScript相关内容可查看本站专题:《JavaScript常用函数技巧汇总》、《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》及《JavaScript数学运算用法总结》

希望本文所述对大家JavaScript程序设计有所帮助。

Javascript 相关文章推荐
可实现多表单提交的javascript函数
Aug 01 Javascript
JavaScript获取当前页面上的指定对象示例代码
Feb 28 Javascript
Node.js(安装,启动,测试)
Jun 09 Javascript
js实现的二分查找算法实例
Jan 21 Javascript
微信小程序 input输入框控件详解及实例(多种示例)
Dec 14 Javascript
JavaScript提高加载和执行效率的方法
Feb 03 Javascript
Node.js查找当前目录下文件夹实例代码
Mar 07 Javascript
node.js将MongoDB数据同步到MySQL的步骤
Dec 10 Javascript
从setTimeout看js函数执行过程
Dec 19 Javascript
Node.js折腾记一:读指定文件夹,输出该文件夹的文件树详解
Apr 20 Javascript
解决IOS端微信H5页面软键盘弹起后页面下方留白的问题
Jun 05 Javascript
小程序wx.getUserProfile接口的具体使用
Jun 02 Javascript
详解ES6 CLASS在微信小程序中的应用实例
Apr 24 #Javascript
Vue中函数防抖节流的理解及应用实现
Apr 24 #Javascript
vue 路由懒加载中给 Webpack Chunks 命名的方法
Apr 24 #Javascript
vue中使用带隐藏文本信息的图片、图片水印的方法
Apr 24 #Javascript
基于Vue实现微前端的示例代码
Apr 24 #Javascript
Vue通过getAction的finally来最大程度避免影响主数据呈现问题
Apr 24 #Javascript
小程序开发之模态框组件封装
Apr 23 #Javascript
You might like
WINDOWS下php5.2.4+mysql6.0+apache2.2.4+ZendOptimizer-3.3.0配置
2008/03/28 PHP
PHP IPV6正则表达式验证代码
2010/02/16 PHP
PHP和Mysqlweb应用开发核心技术-第1部分 Php基础-2 php语言介绍
2011/07/03 PHP
基于PHP字符串的比较函数strcmp()与strcasecmp()的使用详解
2013/05/15 PHP
解析PHP中VC6 X86和VC9 X86的区别及 Non Thread Safe的意思
2013/06/28 PHP
thinkphp3.2.2实现生成多张缩略图的方法
2014/12/19 PHP
PHP中模拟链表和链表的基本操作示例
2016/02/27 PHP
Yii2实现同时搜索多个字段的方法
2016/08/10 PHP
Jquery 动态添加按钮实现代码
2010/05/06 Javascript
基于jQuery试卷自动排版系统
2010/07/18 Javascript
JavaScript 处理Iframe自适应高度(同或不同域名下)
2013/03/29 Javascript
在js中判断checkboxlist(.net控件客户端id)是否有选中
2013/04/11 Javascript
nodejs中实现阻塞实例
2015/03/24 NodeJs
快速学习AngularJs HTTP响应拦截器
2015/12/31 Javascript
理解javascript定时器中的单线程
2016/02/23 Javascript
面包屑导航详解
2017/12/07 Javascript
通过vue-cli来学习修改Webpack多环境配置和发布问题
2017/12/22 Javascript
javascript匿名函数中的'return function()'作用
2018/10/15 Javascript
AngularJS上传文件的示例代码
2018/11/10 Javascript
JS前端知识点offset,scroll,client,冒泡,事件对象的应用整理总结
2019/06/27 Javascript
js刷新页面location.reload()用法详解
2019/12/09 Javascript
Python使用matplotlib实现在坐标系中画一个矩形的方法
2015/05/20 Python
详解Python中open()函数指定文件打开方式的用法
2016/06/04 Python
使用rst2pdf实现将sphinx生成PDF
2016/06/07 Python
Django 项目重命名的实现步骤解析
2019/08/14 Python
简单了解python filter、map、reduce的区别
2020/01/14 Python
python tkinter之 复选、文本、下拉的实现
2020/03/04 Python
在Python中用GDAL实现矢量对栅格的切割实例
2020/03/11 Python
html5 touch事件实现触屏页面上下滑动(二)
2016/03/10 HTML / CSS
墨尔本复古时尚品牌:Dangerfield
2018/12/12 全球购物
美国美食礼品篮网站:Gourmet Gift Baskets
2019/12/15 全球购物
英国鞋网:Rubber Sole
2020/03/03 全球购物
遗体告别仪式主持词
2014/03/20 职场文书
教师节倡议书
2014/08/30 职场文书
2016年教师学习教师法心得体会
2016/01/20 职场文书
浅谈Python中的函数(def)及参数传递操作
2021/05/25 Python