前言

ES6 发布到现在差不多有5年时间了。在这5年时间里ES6摧枯拉朽般的将现代前端“改朝换代”,Promise是其中“大将”般的存在,影响着无数的前端库和API。可以这么说,Promise已经是现代前端的“血液”。
尽管经过5年的日日夜夜,尽管书写过数不尽的Promise。面对着这个时而让我们感到真棒,用的舒服、时而坑得我们踉踉跄跄的API,我们真的了解它吗?

陌生情景一:怎么和循环结合

相信许多开发者最开始对Promise感到陌生的情景就是:不知道怎么跟循环结合使用。 例如:
// 我想将数组下的每个元素都执行一个函数
fetchSomeData().then((res) => {
res.data.forEach((item) => {
doSomethingFunction(item);
})
}).then(res => {
// 做其他事
})

  

这个例子有什么问题呢?
问题在于:第一个then回调函数返回的是undefined,就是说第二个then函数并没有等doSomethingFunction(item);执行完。事实上,它并不需要等待任何事情,并且可以在doSomethingFunction(item);执行了几个后执行。
这是一个非常隐蔽的错误,因为如果res.data足够小或者doSomethingFunction()执行的足够快,可能就不会发现任何问题。
如何解决?需要用到Promise.all()。

Promise.all()

fetchSomeData().then(res) => {
return Promise.all(res.data.map(item) => {
return doSomethingFunction(item);
})
}).then(res => {
// 做其他事
})

  

Promise.all接收一个Promise对象组成的数组作为参数,当这个数组所有的Promise对象状态都变成resolved或者rejected的时候,它才会去调用then方法。

陌生情景二:没有return

fetchSomeData().then((res) => {
doSomethingFunction(res);
}).then(res => {
// 做其他事
})

  

这个例子的问题在于第二个then函数获取的是undefined。使用了side effect去改变而不是返回。
每一个Promise都有一个then方法,我们能在then方法中做三件事情:
  • return 另一个Promise
  • return 一个值
  • throw 一个错误

返回一个Promise

fetchSomeData().then((res) => {
return getId(res);
}).then(res => {
// 我能得到id
})

  

使用return 返回第二个Promise,在第二个then方法中就能得到id。如果没有return,那么getId()只是一个side effect,那么第二个then方法只能得到undefined。

返回一个值

比如说要对id做一个缓存处理,以降低运行时间。
fetchSomeData().then((res) => {
if (idCache[id]) {
return idCache[id];
}
return getId(res);
}).then(res => {
// 我能得到id
})

  

不管id是缓存中的,还是异步去获取的,都能返回正确的。

throw error

throw error能让Promise变得更严谨。如果要在用户登出的时候做错误处理:
fetchSomeData().then((res) => {
if (logout) {
throw new Error('用户已登出');
}
if (idCache[id]) {
return idCache[id];
}
return getId(res);
}).then(res => {
// 我能得到id
}).catch(err=> {
// 做错误处理
})

  

catch方法能获取得到错误。

陌生情景三:不知道Promise.resolve()与Promise.reject()

如果经常写出下面内容:
new Promise((resolve, reject) => {
resolve(doSomething())
}).then(...)

  

其实就是对Promise不熟悉,可以用更简短的语句去表达

Promise.resolve

Promise.resolve(doSomething()).then(...)

  

同样Promise.reject()可以返回立即被拒绝的Promise

Promise.reject

Promise.reject(new Error('some error'))

  

陌生情景四:then().catch()与then(resolveHandler, rejectHandler)傻傻分不清楚

其实catch方法是then(null, function(err) {})的语法糖
下面这两段代码是相等的
promise().catch(err => {
// 处理错误
}) promise().then(null, err => {
// 处理错误
})

  

但并不意味着下面这两段代码是相等的
promise().then((res) => {
return otherPromise(res);
}).cathc(err => {
// 能捕获得到错误
}) promise().then(res => {
return otherPromise(res);
}, err => {
// 不能捕获得到错误
})

  

所以,当使用then(resolveHandler, rejectHandler)时,如果它本身发生错误,rejectHandler是不会捕获得到的。
出于这个原因,捕获错误尽量使用catch方法。

陌生情景五:如何依次执行一系列的promise

如果要执行一系列的promise,类似Promise.all()方法,但不会并行执行。可能会写出下面的代码
function execute(promises) {
var result = Promise.resolve();
promise.forEach(promise => {
result = result.then(promise);
});
return result;
}

  

不幸的是,这无法按照预期去执行,仍然是并行执行的。
发生这种情况的原因是:预期是不希望对一系列的promise进行操作。但是根据promise规范,一旦创建了promise,它就会开始执行。
因此要用到promise工厂函数
function execute(promiseFactories) {
var result = Promise.reslove();
promiseFactories.forEach(promiseFactory => {
result = result.then(promiseFactory);
});
return result;
}

  

promise工厂函数非常简单,只是一个返回promise的函数
function promiseFactory() {
return promiseCreated();
}

  

这种方法之所以会有效,是因为promise工厂函数直到被调用时才创建promise。它与then函数的工作方式相同

陌生情景六:then方法的使用

你认为下面代码的输出是什么?
Promise.resolve('foo').then(Promise.resolve('bar')).then((res) => {
console.log(res);
})

  

如果你认为输出bar,那就错了。实际上输出的是foo!
因为当传递给then()方法并非是一个函数时,它实际上执行then(null),这样先前的promise结果就无法传给第二个then方法。
Promise.resolve('foo').then(null).then(res => {
console.log(res) // foo
})

  

简而言之,可以将promise直接传给then方法,但它并不会按照你的预期去执行。所以你要这样做
Promise.resolve('foo').then(() => {
return Promise.resolve('bar')
}).then(res => {
console.log(res); // bar
})

  

因此,请提醒自己:始终要将函数传递给then方法

总结

有人说:一回生二回熟。
经历了上述这六回,相信对promise就像亲人一般的熟悉。
上述文章是翻译、加工自We have a problem with promises
 
作者:Nolan Lawson
来源:github

熟悉而陌生API:Promise的更多相关文章

  1. Python : 熟悉又陌生的字符编码(转自Python 开发者)

    Python : 熟悉又陌生的字符编码 字符编码是计算机编程中不可回避的问题,不管你用 Python2 还是 Python3,亦或是 C++, Java 等,我都觉得非常有必要厘清计算机中的字符编码概 ...

  2. Axios & fetch api & Promise & POST

    Axios & fetch api & Promise & POST https://github.com/axios/axios https://appdividend.co ...

  3. NSUserDefaults:熟悉与陌生(转)

    转载自:http://swiftcafe.io/2016/04/04/nsuserdefaults/?hmsr=toutiao.io&utm_medium=toutiao.io&utm ...

  4. 熟悉而陌生的新朋友——IAsyncDisposable

    本文作者--句幽 在.NET Core 3.0的版本更新中,官方我们带来了一个新的接口 IAsyncDisposable. 小伙伴一看肯定就知道,它和.NET中原有的IDisposable接口肯定有着 ...

  5. 第三讲:ifconfig:最熟悉又陌生的命令行

    你知道怎么查看IP地址吗? 当面试听到这个问题的时候,面试者常常会觉得走错了房间.我面试的是技术岗位啊,怎么问这么简单的问题? 的确,即便没有专业学过计算机的人,只要倒腾过电脑,重装过系统,大多也会知 ...

  6. 那些熟悉又陌生的 css2、css3 样式,持续复习

    initial关键字:    除了 Internet Explorer,其他的主流浏览器都支持 initial 关键字. Opera 15 之前的版本不支持 initial 关键字. initial ...

  7. 3(计算机网络)ifconfig:最熟悉又陌生的命令行

    当面试听到这个问题的时候,面试者常常会觉得走错了房间.我面试的是技术岗位啊,怎么问这么简单的问题? 的确,即便没有专业学过计算机的人,只要倒腾过电脑,重装过系统,大多也会知道这个问题的答案:在 Win ...

  8. 趣谈网络协议-第3讲 | ifconfig:最熟悉又陌生的命令行

    如何查看IP地址呢? windows  查看IP地址命令  IPCONFIG LINUX    查看IP 命令   IFCONFIG   IP ADDR ifconfig 和ADDR的区别  这是一个 ...

  9. 【微信小程序项目实践总结】30分钟从陌生到熟悉 web app 、native app、hybrid app比较 30分钟ES6从陌生到熟悉 【原创】浅谈内存泄露 HTML5 五子棋 - JS/Canvas 游戏 meta 详解,html5 meta 标签日常设置 C#中回滚TransactionScope的使用方法和原理

    [微信小程序项目实践总结]30分钟从陌生到熟悉 前言 我们之前对小程序做了基本学习: 1. 微信小程序开发07-列表页面怎么做 2. 微信小程序开发06-一个业务页面的完成 3. 微信小程序开发05- ...

随机推荐

  1. 转载--对batch normalization的理解

    转载的大神的: https://www.cnblogs.com/guoyaohua/p/8724433.html 上边这个应该是抄的下边这个原文,但是上边的有重点标记 https://blog.csd ...

  2. 什么是Redis?

    Remote Dictionary Server(Redis)是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value 数据库,并提供多种语言的API.它通常被 ...

  3. JDK,JRE,JVM三者之间的关系和作用

    1,定义: JDK: Java Develpment Kit java 开发工具 bin:最主要的是编译器(javac.exe) include:java和JVM交互用的头文件 lib:类库 JRE: ...

  4. esx.problem.hyperthreading.unmitigated

    是因为VMware新发布的一个漏洞补丁导致的具体解释可参阅VMware官方kb,有详细解释和版本说明. 可选择屏蔽该问题告警 选中主机>配置>高级系统设置>编辑>修改" ...

  5. Kubernetes项目简介

    Kubernetes项目简介 Kubernetes 是 Google 团队发起的开源项目,它的目标是管理跨多个主机的容器,提供基本的部署,维护以及运用伸缩,主要实现语言为 Go 语言.Kubernet ...

  6. linux下用户管理命令、用户组管理命令

    useradd 添加新用户 1.基本语法 useradd 用户名                   (功能描述:添加新用户) useradd -g 组名 用户名      (功能描述:添加新用户到某 ...

  7. [每日一题]面试官问:for in和for of 的区别和原理?

    关注「松宝写代码」,精选好文,每日一题 ​时间永远是自己的 每分每秒也都是为自己的将来铺垫和增值 作者:saucxs | songEagle 一.前言 2020.12.23 日刚立的 flag,每日一 ...

  8. String -- 从源码剖析String类

    几乎所有的 Java 面试都是以 String 开始的,String 源码属于所有源码中最基础.最简单的一个,对 String 源码的理解也反应了你的 Java 基础功底. String 是如何实现的 ...

  9. CS系统中分页控件的制作

    需求:在一个已有的CS项目(ERP中),给所有的列表加上分页功能. 分页的几个概念: 总记录数  totalCount (只有知道了总记录数,才知道有多少页) 每页记录数  pageSize (根据总 ...

  10. 手把手教你搭建SSH框架(Eclipse版)

    原文来自公众号[C you again],若需下载完整源码,请在公众号后台回复"ssh". 本期文章详细讲解了SSH(Spring+SpringMVC+Hibernate)框架的搭 ...