查看原文
其他

两行代码的库引发“血案”:坑了数百万个项目

马超 程序人生 2020-10-29

作者 | 马超
责编 | 伍杏玲
出品 | 程序人生(ID:coder_life) 

4月25日,一个体量很小的 JavaScript 库is-promise 进行了更新。

由于最新版本没有遵循正确的 ES 模块标准,使得超过300万个引用了is-promise的前端项目均出现了问题,这个问题甚至让整个 JavaScript 生态系统陷入了混乱。由于前端项目的构造方式与中后台项目的机制不同,这种由小型 JavaScript 项目引起广泛问题的情况已经不是第一次发生了。这次的问题并没有导致现有 JS 项目崩溃,主要问题是无法编译新版本。

当笔者看到这个新闻,心中不由得一颤,因为司徒正美老师生前一直奋斗的前端技术领域,当我来写这篇文章时,执笔当哭,缅怀旧人。

笔者和司徒结识于CSDN,记得是去年6月,当时我看到一篇博文《前端开发 20 年变迁史》,心中不由赞叹,竟有人能将前端技术上升到哲学高度来进行阐释,于是千方百计地找到了司徒的微信加为好友,和司徒聊天中,明显能感受到他对于前端技术的理解深度和积极热情。谁知天有不测风云,司徒年纪轻轻竟然溘然长逝,在此笔者也提示各位读者朋友们保重身份,切莫透支健康。

下面笔者帮助大家解读一下这则新闻背后的技术细节。由于前端并不是笔者的领域,如有错漏还请各位指正。


初识promise


因为笔者对于JavaScript也不是特别了解,在初步学习后我看到了阻塞代码、非阻塞代码、事件驱动设计模式、事件生命周期、函数堆栈、事件队列等概念,以及polyfill、babel、angular、reactJS、Vue.JS 等框架。

JavaScript是单线程执行代码,但是由于具备非阻塞和回调机制,JavaScript也可以实现异步功能。于是有了Promise机制。

根据MDN上对于Promise机制的描述(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise):Promise对象是一个代理对象(代理一个值),被代理的值在Promise对象创建时可能是未知的。它允许你为异步操作的成功和失败分别绑定相应的处理方法(handlers)。这让异步方法可以像同步方法那样返回值,但并不是立即返回最终执行结果,而是一个能代表未来出现的结果的promise对象。

示例及注释如下:

//创建一个Promise对象,定义resolve方法,在3000ms后执行。
const promise1 = new Promise(function(resolve, reject) {
  setTimeout(function() {
    resolve('promise');
  }, 3000);
});

//非阻塞执行promise1,在promise完成后此方法会被执行。
promise1.then(function(value) {
  console.log(value);
  // 3s后会输出promise
});

console.log(promise1);
//直接输出 [object Promise]

综上笔者认为可以将Promise理解成Java中的Callback,用以帮助JavaScript实现异步功能。

本次出现问题的is-promise包,作用是测试一个JavaScript对象是否为Promise的。is-promise包的代码其实非常短,其主要的功能实现代码只有两行而已。

前端开发人员在引入is-promise包之后,就能在自己的项目中引用它,而且is-promise包是基于MIT协议的,因此引用该项目,也不必须要求开源。

虽然只有两行代码,但is-promise 库却是当今最受欢迎的 JavaScript 软件包之一。据不完全统计,is-promise是700余个知名的JavaScript 库的依赖项,其影响项目数量至少超过300万,范围涵盖至封闭源 JavaScript 代码库和 JavaScript 生态系统中一些最大的项目。

其中包括有:Facebook 的 Create React App(用于创建 React 应用程序的标准模板)、谷歌的 Angular.js 框架、谷歌的 Firebasse-tools、亚马逊的 AWS Serverless CLI、Nuxt.js 和 AVA 等。


引发问题的ES 模块标准


is-promise 库之所以引发问题,关键在于没有遵循正确的ES模块标准,而提到ES模块标准(EMS),我们还要从最基本的概念聊起。我们知道JavaScript是一门动态的脚本化语言,它让前端页面的开发变得非常简单。

JavaScript的编程范式抽象成维护变量、赋值和计算操作。大量的代码用于操作变量,开发者需要懂得如何去组织和维护这些变量。JavaScript 提供了一种方式,即函数作用域。

在一个函数内只需要考虑这个函数的变量问题,不必去担心其他函数会操作这些变量。

随之带来的问题是变量无法共享,无法在不同的函数之间相互共享变量。如果想要在作用域外共享变量,只能通过外层作用域,或者全局作用域。

ES模块标准是提供了更好的方式来组织变量和函数,把相关的变量和函数组织到一起。具体就是将这些函数和变量放到一个模块作用域内,实现在模块间共享变量。与函数作用域不同的是,模块内部的变量实现了在其他模块内共享。

还可指定哪些变量、类或者函数可以共享。在其他模块中共享,被称为 export。这就出现了模块间的依赖,这是一种很明确的关系,当移除一个模块时可以准确地知道哪些模块会出错。一旦有了模块间导出和引用变量的能力,我们就可以将代码打成小包。然后就可以像乐高玩具那样组合,再组合。使用小模块就可以创建出各类应用。在使用模块的时候,其实就是在做一个依赖关系图。ESM的模块包括三个过程:

1、构建:下载,解析,然后把文件解析为模块记录;

2、实例化:为模块分内存空间(此时还没赋值),然后依照导入,导出语句把模块指向内存地址,这个过程叫链接;

3、运行(求值):运行代码的时候,才会给内存空间填充真实的值。

EMS则通过一系列的标准来确保相关代码可以实现上述模块化的功能。

is-promise v.2.2.0 版本却未遵循正确的 ES 模块标准。因此在其更新发布后,引用了is-promise在各个项目都在的构建链时出现了问题。

总结一下本文内容,Promise是Javascript中实现异步功能的重要机制,is-promise又是目前流行度最高的promise对象检测工具,而由于is-promise没有遵循正确的ES模块标准,使得其它引用了is-promise包的程序出现了问题。

最后引用司徒正美老师在《前端开发 20 年变迁史》的话来做结束:“当初JavaScript被误解为最糟糕的语言,时至今日它是最流行的语言,GitHub 60%的项目都是与JavaScript有关……任何可以使用JavaScript来编写的应用,最终会由JavaScript编写。

愿前端开发之路越来越好!

npm地址:https://www.npmjs.com/package/p-is-promise

Github地址:https://github.com/sindresorhus/p-is-promise

更多精彩推荐

当互联网码农遇见国企老同学

一个月面试近 20 家,拿下阿里 Offer!

华为海思超越高通,一季度国内占有率第一;苹果 iOS 13.5 优化 Face ID;Ruby 2.4 结束支持 | 极客头条

AI图像智能修复老照片,效果惊艳到我了

程序员内功修炼系列:10 张图解谈 Linux 物理内存和虚拟内存

当 DeFi 遇上 Rollup,将擦出怎样的火花?

你点的每个“在看”,我都认真当成了喜欢

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存