资讯专栏INFORMATION COLUMN

Thunk深入解析

xi4oh4o / 822人阅读

摘要:捕抓信息,并且出错时,传递给回调函数回调函数应该只调用一次。总结在学习一个概念或者一个模块时,测试代码加深你对知识的理解和掌握。

一步步打造thunkify

本文的思路:

学习thunk相关知识,主要参考阮一峰的介绍

一步步实现thunkify模块,并且使用测试用例来完善我们的代码,打造出一个健壮的模块

1. 诞生背景

Thunk函数的诞生是源于一个编译器设计的问题:求值策略,即函数的参数到底应该何时求值。

例如:

</>复制代码

  1. var x = 1;
  2. function f(m) {
  3. return m * 2;
  4. }
  5. f(x + 5);

其中x+5这个表达式应该什么时候求值,有两种思路

传值调用(call by value),即在进入函数体之间,先计算x+5的值,再将这个值(6)传入函数f,例如c语言,这种做法的好处是实现比较简单,但是有可能会造成性能损失。

传名调用(call by name),即直接将表达式(x+5)传入函数体,只在用到它的时候求值。

2. Thunk函数的含义

编译器的传名调用实现,往往就是将参数放到一个临时函数之中,再将这个临时函数转入函数体,这个临时函数就叫做Thunk函数

来看一段代码示例:

</>复制代码

  1. function f(m) {
  2. return m*2;
  3. }
  4. f(x + 5);
  5. // 等价于以下代码
  6. var thunk = function () {
  7. return x + 5;
  8. };
  9. function f(thunk) {
  10. return thunk() * 2;
  11. }
3. javascript中的Thunk函数

我们都知道Javascript是传值调用的,那么js中的Thunk函数又是怎么回事?

在Javascript语言中,Thunk函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本,且只接受回调函数作为参数。

还是通过代码来理解,即

</>复制代码

  1. // 正常版本的readFile,需要两个参数filename、callback
  2. fs.readFile(fileName, callback);
  3. // thunk版本的readFile
  4. var readFileThunk = thunkify(fs.readFile);
  5. readFileThunk(fileName)(callback);

原文中例子就是柯里化,预置参数fileName,直接调用fs.readFile

好,现在我们来思考如何实现thunkify函数。我们从调用的形式来看,返回的应该是一个高阶函数,即返回一个函数a,a的返回还是一个函数。

</>复制代码

  1. var thunkify = function (fn) {
  2. return function () {
  3. return function () {
  4. }
  5. }
  6. };

结合上述例子,因为是包装函数,因此最终还是readFile执行,且需要fileName,因此:

</>复制代码

  1. var thunkify = function (fn) {
  2. return function () {
  3. var args = Array.prototype.slice.call(arguments);
  4. return function (callback) {
  5. args.push(callback);
  6. return fn.apply(this, args);
  7. }
  8. }
  9. };

这样似乎很完美,我们运行整个示例

</>复制代码

  1. const fs = require("fs");
  2. var thunkify = function (fn) {
  3. return function () {
  4. var args = Array.prototype.slice.call(arguments);
  5. return function (callback) {
  6. args.push(callback);
  7. return fn.apply(this, args);
  8. }
  9. }
  10. };
  11. var readFileThunk = thunkify(fs.readFile);
  12. readFileThunk("test.txt", "utf-8")( (err, data) => {
  13. console.log(data);
  14. });

运行结果为

4. 打造thunkify模块

要写出一个健壮的thunkify函数,需要考虑的各种情况,而我们通过tj大神写的thunkify模块的测试代码,来看看我们自己的thunkify还存在哪些不足,一步步来优化。

1、保存上下文的问题

</>复制代码

  1. function load(fn) {
  2. fn(null, this.name);
  3. }
  4. var user = {
  5. name: "tobi",
  6. load: thunkify(load)
  7. };
  8. user.load()((err, res) => {
  9. console.log(res);
  10. });

运行之后,res的结果为undefined,原因是没有保存上下文,改进一下

</>复制代码

  1. var thunkify = function (fn) {
  2. return function () {
  3. var args = Array.prototype.slice.call(arguments);
  4. var ctx = this;
  5. return function (callback) {
  6. args.push(callback);
  7. return fn.apply(ctx, args);
  8. }
  9. }
  10. };

2、捕抓错误

</>复制代码

  1. function load(fn) {
  2. throw new Error("boom");
  3. }
  4. load = thunkify(load);
  5. load()(err => console.log(err.message));

运行之后,发现并没有捕抓到错误,我们需要执行函数进行try/catch,并且当出错时,传递出错信息。

</>复制代码

  1. var thunkify = function (fn) {
  2. return function () {
  3. var args = Array.prototype.slice.call(arguments);
  4. var ctx = this;
  5. return function (callback) {
  6. args.push(callback);
  7. var result;
  8. // try/catch捕抓信息,并且出错时,传递给回调函数
  9. try {
  10. result = fn.apply(ctx, args);
  11. } catch (e) {
  12. callback(e);
  13. }
  14. return result;
  15. }
  16. }
  17. };

3、回调函数应该只调用一次。

</>复制代码

  1. function load(fn) {
  2. fn(null, 1);
  3. fn(null, 2);
  4. fn(null, 3);
  5. }
  6. load = thunkify(load);
  7. load()((err,ret) => console.log(ret));

运行输出结果为1 2 3,而我们期望结果只为1,那么需要判断callback是否已经执行过了,使其只执行一次。

</>复制代码

  1. var thunkify = function (fn) {
  2. return function () {
  3. var args = Array.prototype.slice.call(arguments);
  4. var ctx = this;
  5. return function (callback) {
  6. var called;
  7. // 对callback进行封装,使其只能执行一次。
  8. args.push(function () {
  9. if(called) return;
  10. called = true;
  11. callback.apply(null, arguments);
  12. });
  13. var result;
  14. try {
  15. result = fn.apply(ctx, args);
  16. } catch (e) {
  17. callback(e);
  18. }
  19. return result;
  20. }
  21. }
  22. };

到这里,我们通过了所有的测试,完成了一个健壮thunkify模块。

5. 总结

在学习一个概念或者一个模块时,测试代码加深你对知识的理解和掌握。

来源

Thunk-阮一峰

thunkify-tj

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/80944.html

相关文章

  • redux深入进阶

    摘要:上一篇文章讲解了如何使用,本篇文章将进一步深入,从的源码入手,深入学习的中间件机制。的功能是让支持异步,让我们可以在中跟服务器进行交互等操作,而他的实现。。。 上一篇文章讲解了redux如何使用,本篇文章将进一步深入,从redux的源码入手,深入学习redux的中间件机制。在这里我们会以一个redux-thunk中间件为例,逐步分解redux的中间机制如何操作,如何执行。 闲话不多说,...

    omgdog 评论0 收藏0
  • 【源码解析】redux-thunk

    摘要:的返回值是函数,这个函数经调用,传入参数,之后会在中间件链上进行传递,只要保证每个中间件的参数是并且将传递给下一个中间件。 了解了Redux原理之后,我很好奇Redux中间件是怎么运作的,于是选了最常用的redux-thunk进行源码分析。 此次分析用的redux-thunk源码版本是2.2.0,redux源码版本是3.7.2。并且需要了解Redux原理 redux中间件都是由redu...

    simpleapples 评论0 收藏0
  • 重读redux源码(二)

    摘要:函数组合,科里化的串联结合示例源码,实现也很优雅,对于返回的,将等参数传递进去,然后执行,等待回调异步完成再。对于正常对象则进行下一步。前言 作为前端状态管理器,这个比较跨时代的工具库redux有很多实现和思想值得我们思考。在深入源码之前,我们可以相关注下一些常见问题,这样带着问题去看实现,也能更加清晰的了解。 常见问题 大概看了下主要有这么几个: redux三大原则 这个可以直接参考...

    dingda 评论0 收藏0
  • 纯Redux原理分析

    摘要:调用链中最后一个会接受真实的的方法作为参数,并借此结束调用链。总结我们常用的一般是除了和之外的方法,那个理解明白了,对于以后出现的问题会有很大帮助,本文只是针对最基础的进行解析,之后有机会继续解析对他的封装 前言 虽然一直使用redux+react-redux,但是并没有真正去讲redux最基础的部分理解透彻,我觉得理解明白redux会对react-redux有一个透彻的理解。 其实,...

    sumory 评论0 收藏0
  • 深入前端-JavaScript异步编程

    摘要:缺点无法取消当处于状态时,无法得知目前进展到哪一个阶段错误不能被生成器什么是函数是提供的一种异步编程解决方案,语法行为与传统函数完全不同函数有多种理解角度。 JavaScript的执行机制在上篇文章中进行了深入的探讨,那么既然是一门单线程语言,如何进行良好体验的异步编程呢 回调函数Callbacks 当程序跑起来时,一般情况下,应用程序(application program)会时常通...

    2json 评论0 收藏0

发表评论

0条评论

xi4oh4o

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<