从事前端开发至今,异步问题经历了 Callback Hell 的绝望,Promise/Deffered 的规范混战,到 Generator 的所向披靡,到如今 Async/Await 为大众所接受,这其中 Promise 和 Async/Await 依然活跃代码中,对他们的认识和评价也经历多次反转,也有各自的拥趸,形成了一直延续至今的爱恨情仇,其背后的思考和启发,依旧值得我们深思。
预先声明:
本文的目标并不是引发大家的论战,也不想去推崇其中任何一种方式来作为前端异步的唯一最佳实践,想在介绍下 Promise 和 Async/Await 知识和背后的趣事的基础上,探究下这些争议下隐藏的共识。
Promise 是异步编程的一种解决方案,相对于传统的回调地狱更加合理和强大。
所谓 Promise,简单来说就是一个容器,里面存储个未来才会结束的时间的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。其内部状态如下:
状态之间的流转是不可逆的,代码书写如下:
function httpPromise(): Promise<{ success: boolean; data: any }> {
return new Promise((resolve, reject) => {
try {
setTimeout(() => {
resolve({ success: true, data: {} });
}, 1000);
} catch (error) {
reject(error);
}
});
}
httpPromise().then((res) => {}).catch((error) => {}).finally(() => {});
从语法角度上看,更加容易理解,但是美中不足的就是不够简洁,无法断点,冗余的匿名函数。
在刚入行的时候也去研究过《如何实现一个 Promise》这个课题,尝试写了下如下的代码。
class promise {
constructor(handler) {
this.resolveHandler = null;
this.rejectedHandler = null;
setTimeout(() => {
handler(this.resolveHandler, this.rejectedHandler);
}, 0);
}
then(resolve, reject) {
this.resolveHandler = resolve;
this.rejectedHandler = reject;
returnthis;
}
}
function getPromise() {
return new promise((resolve, reject) => {
setTimeout(() => {
resolve(20);
}, 1000);
});
}
getPromise().then((res) => {
console.log(res);
}, (error) => {
console.log(error);
});
虽然羞耻,但是不得不说当时还是挺满足的,后面发现无法解决异步注册的问题。
const promise1 = getPromise();
setTimeout(() => {
promise1.then((data) => {
console.log(data);
}).catch((error) => {
console.error(error);
});
}, 0);
function getFPromise() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(20), 1000);
});
}
// 执行情况 报错
// Uncaught TypeError: promise1.then(...).catch is not a function
// Uncaught TypeError: resolve is not a function
// vs 官方Promise实现
const promise2 = getFPromise();
setTimeout(() => {
promise2.then((data) => {
console.log(data);
}).catch((error) => {
console.error(error);
});
}, 0);
// 执行情况,符合预期
// 20
对于这一部分有兴趣的同学可以自行查找标准版的实现方案,不过在这个探索过程中确实勾起对基础知识的兴趣,这也是本文去挖掘这部分知识的初衷。
接下来看看 Async/Await 吧。
Async/Await 并不是什么新鲜的概念,事实的确如此。
早在 2012 年微软的 C# 语言发布 5.0 版本时,就正式推出了 Async/Await 的概念,随后在 Python 和 Scala 中也相继出现了 Async/Await 的身影。再之后,才是我们今天讨论的主角,ES 2016 中正式提出了 Async/Await 规范。
以下是一个在 C# 中使用 Async/Await 的示例代码:
public async Task<int> SumPageSizesAsync(IList<Uri> uris)
{
int total = 0;
foreach (var uri in uris) {
statusText.Text = string.Format("Found {0} bytes ...", total);
var data = await new WebClient().DownloadDataTaskAsync(uri);
total += data.Length;
}
statusText.Text = string.Format("Found {0} bytes total", total);
return total;
}
再看看在 JavaScript 中的使用方法:
async function httpRequest(value) {
const res = await axios.post({ ...value });
return res;
}
好的设计总是会想借鉴的,不寒碜。
其实在前端领域,也有不少类 Async/Await 的实现,其中不得不提到的就是知名网红之一的老赵写的 wind.js ,站在今天的角度看,windjs 的设计和实现不可谓不超前。
ES2017 标准引入了 async 函数,使得异步操作变得更加方便。
这里引用阮一峰老师的描述:
async 函数是什么?一句话,它就是 Generator 函数的语法糖。
前文有一个 Generator 函数,依次读取两个文件。
const fs = require('fs');
const readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) return reject(error);
resolve(data);
});
});
};
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
上面代码的函数 gen
可以写成 async
函数,就是下面这样。
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
一比较就会发现,async
函数就是将 Generator 函数的星号(*
)替换成 async
,将 yield
替换成 await
,仅此而已。
相对于 Generator 的改进主要集中集中在:
到这里大家会发现,Async/Await 本质也是 Promise 的语法糖:Async 函数返回了 Promise 对象。
来看下实际 Babel 转化的代码,方便大家理解下
async function test() {
const img = await fetch('tiger.jpg');
}
// babel 转换后
'use strict';
var test = function() {
var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee() {
var img;
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case0:
_context.next = 2;
return fetch('tiger.jpg');
case2:
img = _context.sent;
case3:
case'end':
return _context.stop();
}
}
}, _callee, this);
}));
return function test() {
return _ref.apply(this, arguments);
};
}();
function _asyncToGenerator(fn) {
return function() {
var gen = fn.apply(this, arguments);
return new Promise(function(resolve, reject) {
function step(key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(function(value) {
step("next", value);
}, function(err) {
step("throw", err);
});
}
}
return step("next");
});
};
}
不难看出最终还是转换成基于 Promise 的调用,但是本来的三行代码被转换成 52 行代码,在一些场景下就带来了成本。
例如 Vue3 并没有采用?.(可选链式操作符符号)的原因:
虽然使用?很简洁,但是实际打包下反而更加冗余了,增加了包的体积,影响 Vue3 的加载速度,这也是 Async/Await 这类简洁语法的痛点。
暂时不考虑深层次的运行性能,仅仅考虑代码使用方式来看,Async/Await 是否完美无缺?
以 “请求 N 次重试” 的实现为例:
/**
* @description: 限定次数来进行请求
* @example: 例如在5次内获取到结果
* @description: 核心要点是完成tyscript的类型推定,其次高阶函数
* @param T 指定返回数据类型,M指定参数类型
*/
export default function getLimitTimeRequest<T>(task: any, times: number) {
// 获取axios的请求实例
let timeCount = 0;
async function execTask(resolve, reject, ...params: any[]): Promise<void> {
if (timeCount > times) {
reject(newError('重试请求失败'));
}
try {
const data: T = await task(...params);
if (data) {
resolve(data);
} else {
timeCount++;
execTask(resolve, reject, params);
}
} catch (error) {
timeCount++;
execTask(resolve, reject, params);
}
}
return function <M>(...params: M[]): Promise<T> {
return new Promise((resolve, reject) => {
execTask(resolve, reject, ...params);
});
};
}
常见的实现思路是将 Promise 的 Resolve、Reject 的句柄传递到迭代函数中,来控制 Promise 的内部状态转化,那如果用 Async/Await 如何做?很明显并不好做,暴露了它的一些不足:
当然,站在 EMCA 规范的角度来看,有些需求可能比较少见,但是如果纳入规范中,也可以减少前端程序员在挑选异步流程控制库时的纠结了。
针对前端异步的处理方案,Promise 和 Async/Await 都是优秀的处理方案,但是美中不足的是有一定的不足,随着前端工程化的深入,一定会有更好的方案来迎合解决这些问题,大家不要失望,未来还是可期的。
从 Promise 和 Async/Await 的演进和纠结中,大家实际能够感到前端人对 JavaScript 世界的辛苦耕作和奇思妙想,这种思维和方式也可以沉淀到我们日常的需求开发中去,善于求索,辩证的去使用它们,追求更加极致的方案。
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/4UvbkOGHBg0OFsLyMHlxVQ
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。