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

本文实例讲述了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.jb51.net/code/HtmlJsRun测试上述代码运行效果。

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

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

(0)

相关推荐

  • javascript 终止函数执行操作

    1.如果终止一个函数的用return即可,实例如下:function testA(){    alert('a');    alert('b');    alert('c');}testA(); 程序执行会依次弹出'a','b','c'. function testA(){    alert('a');    return;    alert('b');    alert('c');}testA(); 程序执行弹出'a'便会终止. 2.在函数中调用别的函数,在被调用函数终止的同时也希望调用的函数

  • js中回调函数的学习笔记

    回调函数是什么在学习之前还真不知道js回调函数怎么使用及作用了,下面本文章把我在学习回调函数例子给各位同学介绍一下吧,有需了解的同学不防进入参考. 回调函数原理: 我现在出发,到了通知你" 这是一个异步的流程,"我出发"这个过程中(函数执行),"你"可以去做任何事,"到了"(函数执行完毕)"通知你"(回调)进行之后的流程 例子 1.基本方法 <script language="javascript&q

  • javascript实现的像java、c#之类的sleep暂停的函数代码

    下面就是我的实现的方法: 代码 复制代码 代码如下: <script type="text/javascript"> function test() { var s="javascript--暂停函数"; setTimeout( function(){ eval(sleep(test)); //执行return之后的代码,也就是"alert(s);" }, 2000 ); //两秒后执行 return; //运行到这就退出 alert

  • JavaScript构造函数详解

    构造函数就是初始化一个实例对象,对象的prototype属性是继承一个实例对象. 构造函数注意事项: 1.默认函数首字母大写 2.构造函数并没有显示返回任何东西.new 操作符会自动创建给定的类型并返回他们,当调用构造函数时,new会自动创建this对象,且类型就是构造函数类型. 3.也可以在构造函数中显示调用return.如果返回的值是一个对象,它会代替新创建的对象实例返回.如果返回的值是一个原始类型,它会被忽略,新创建的实例会被返回. function Person( name){ this

  • js中匿名函数的N种写法

    匿名函数没有实际名字,也没有指针,怎么执行滴? 其实大家可以看看小括号的意义就应该可以理解.小括号有返回值,也就是小括号内的函数或者表达式的返回值,所以说小括号内的function返回值等于小括号的返回值,不难理解 (function(){})()可以将没有名字的函数执行了把- 关于匿名函数写法,很发散~ 最常见的用法: 复制代码 代码如下: (function() { alert('water'); })(); 当然也可以带参数: 复制代码 代码如下: (function(o) { alert

  • js的回调函数详解

    现在做native App  和Web App是主流,也就是说现在各种基于浏览器的web app框架也会越来越火爆了,做js的也越来越有前途.我也决定从后端开发渐渐向前端开发和手机端开发靠拢,废话不说了,我们来切入正题"js的回调函数"相关的东西. 说起回调函数,好多人虽然知道意思,但是还是一知半解.至于怎么用,还是有点糊涂.网上的一些相关的也没有详细的说一下是怎么回事,说的比较片面.下面我只是说说个人的一点理解,大牛勿喷.我们来看一下一个粗略的一个定义"函数a有一个参数,这

  • js 字符串操作函数

    concat() – 将两个或多个字符的文本组合起来,返回一个新的字符串. indexOf() – 返回字符串中一个子串第一处出现的索引.如果没有匹配项,返回 -1 . charAt() – 返回指定位置的字符. lastIndexOf() – 返回字符串中一个子串最后一处出现的索引,如果没有匹配项,返回 -1 . match() – 检查一个字符串是否匹配一个正则表达式. substring() – 返回字符串的一个子串.传入参数是起始位置和结束位置. replace() – 用来查找匹配一个

  • js中对函数设置默认参数值的3种方法

    在javascript中如何为函数设置默认参数值,下面提供几种方法供大家参考. 第一种方法: function example(a,b){ var a = arguments[0] ? arguments[0] : 1;//设置参数a的默认值为1 var b = arguments[1] ? arguments[1] : 2;//设置参数b的默认值为2 return a+b; } 注意以上函数也可写作如下: function example(){ var a = arguments[0] ? a

  • js function定义函数使用心得

    1.最基本的作为一个本本分分的函数声明使用. 复制代码 代码如下: function func(){} 或 var func=function(){}; 2.作为一个类构造器使用: 复制代码 代码如下: function class(){} class.prototype={}; var item=new class(); 3.作为闭包使用: 复制代码 代码如下: (function(){ //独立作用域 })(); 4.可以作为选择器使用: 复制代码 代码如下: var addEvent=ne

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

    本文实例讲述了javascript 函数的暂停和恢复.分享给大家供大家参考,具体如下: javascript 异步编程从来都是一个难题,最开始我们用 callback,但随之触发了回调地狱,于是"发明" Promise 解决 callback 嵌套过深的问题.然而由于滥用 Promise(一连串的 then),代码变得不易阅读了.此时,async-await 横空出世,它让我们可以用同步的方式编写异步代码,简直 amazing,以至于有人说它就是 javascript 异步编程的银弹.

  • JavaScript函数节流概念与用法实例详解

    本文实例讲述了JavaScript函数节流概念与用法.分享给大家供大家参考,具体如下: 最近在做网页的时候有个需求,就是浏览器窗口改变的时候需要改一些页面元素大小,于是乎很自然的想到了window的resize事件,于是乎我是这么写的 <!DOCTYPE html> <html> <head> <title>Throttle</title> </head> <body> <script type="text

  • Python中zip()函数的解释和可视化(实例详解)

    zip()的作用 先看一下语法: zip(iter1 [,iter2 [...]]) -> zip object Python的内置help()模块提供了一个简短但又有些令人困惑的解释: 返回一个元组迭代器,其中第i个元组包含每个参数序列或可迭代对象中的第i个元素.当最短的可迭代输入耗尽时,迭代器将停止.使用单个可迭代参数,它将返回1元组的迭代器.没有参数,它将返回一个空的迭代器. 与往常一样,当您精通更一般的计算机科学和Python概念时,此模块非常有用.但是,对于初学者来说,这段话只会引发更

  • C++子类父类成员函数的覆盖和隐藏实例详解

    C++子类父类成员函数的覆盖和隐藏实例详解 函数的覆盖 覆盖发生的条件: (1) 基类必须是虚函数(使用virtual 关键字来进行声明) (2)发生覆盖的两个函数分别位于派生类和基类 (3)函数名和参数列表必须完全相同 函数的隐藏 隐藏发生的条件: (1)子类和父类的函数名相同,参数列表可以不一样 看完下面的例子就明白了 #include "iostream" using namespace std; class CBase{ public: virtual void xfn(int

  • Javascript之图片的延迟加载的实例详解

    Javascript之图片的延迟加载的实例详解 作用:保证页面打开的速度(3s之内打不开页面,就已经算是死亡页面了) 原理: 1)对于首屏内容中的图片:首先给对应的区域一张默认图片占着位置(默认图片需要非常小,一般可以维持在5kb以内),当首屏内容都加载完成后(或者也可以给一个延迟时间),再开始加载真实图片 2)对于其他屏中的图片:也是给一张默认的图片占位,当滚动条滚动到对应区域的时候,我们再开始加载真实的图片 扩展:数据的异步加载:开始只把前两屏的数据加载绑定出来,后面的数据不进行处理,当页面

  • JavaScript复制变量三种方法实例详解

    这篇文章主要介绍了JavaScript复制变量三种方法实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 直接将一个变量赋给另一个变量时,系统并不会创造一个新的变量,而是将原变量的地址赋给了新变量名.举个栗子: 复制代码 复制代码 let obj = { a: 1, b: 2, }; let copy = obj; obj.a = 5; console.log(copy.a); // Result // a = 5; // 更改obj的值,

  • python Scala函数与访问修辞符实例详解

    目录 常规函数 可变参数函数 使用名字调用函数 匿名函数 访问修饰符 常规函数 object Demo { def main(args: Array[String]) { println( "Returned Value : " + addInt(5,7) ); // 普通调用 println( "Returned Value : " + addInt(a=5,b=7) ); // 指定参数调用 } // 方法 默认参数 b = 7 def addInt( a:In

  • JavaScript设计模式之调停者模式实例详解

    本文实例讲述了JavaScript设计模式之调停者模式.分享给大家供大家参考,具体如下: 1.定义 调停者模式包装了一系列对象相互作用的方式,使得这些对象不必相互明显作用.从而使他们可以松散偶合.当某些对象之间的作用发生改变时,不会立即影响其他的一些对象之间的作用.保证这些作用可以彼此独立的变化.调停者模式将多对多的相互作用转化为一对多的相互作用.调停者模式将对象的行为和协作抽象化,把对象在小尺度的行为上与其他对象的相互作用分开处理. 2.使用的原因 当对象之间的交互操作很多,且每个对象的行为操

  • C++模板基础之函数模板与类模板实例详解

    泛型编程  如果让你编写一个函数,用于两个数的交换.在C语言中,我们会用如下方法: // 交换两个整型 void Swapi(int* p1, int* p2) { int tmp = *p1; *p1 = *p2; *p2 = tmp; } // 交换两个双精度浮点型 void Swapd(double* p1, double* p2) { double tmp = *p1; *p1 = *p2; *p2 = tmp; }  因为C语言不支持函数重载,所以用于交换不同类型变量的函数的函数名是不

  • Javascript中函数分类&this指向的实例详解

    JS中定义函数的三种方式 通过实例来说明吧 <script> //method1 function fn() { console.log('fn created '); } //method2 var fn2 = function () { console.log('fn2 created'); } //method3 var fn3 = new Function('test', 'console.log(test);'); fn3('fn3 test'); console.dir(fn3);

随机推荐