当前位置: 首页 > news >正文

Promise 使用技巧九则

本文译自 9 Promising Promise Tips。

工程师们,你们总说 Pormise 好用!但有时候用起来是不是还很懵逼。本文传授给你九条实用的 Promise 使用技巧,帮助你和它建立起良好的关系!

1. 你可以在 .then 回调里返回 Promise

我必须大声喊出来:

是的!你可以 .then 回调里返回 Promise!

而且,返回的 promise 会在接下来的 .then 被自动打开(unwrapped):

.then(r => {
    // 这是一个 { statusCode: 200 } promise
    return serverStatusPromise(r);
})
.then(resp => {
    // 200;注意上面的 promise 被自动 unwrap 了
    console.log(resp.statusCode);
})

2. 每次调用 .then 都会产生一个新的 Promise

如果你熟悉 JavaScript 的链式调用,对这种用法一定不陌生。

调用 .then 和 .catch 时都会创建一个新的 Promise。这个新的 Promise 可以继续使用 .then 或者 .catch

var statusProm = fetchServerStatus();

var promA = statusProm.then(r => (r.statusCode === 200 ? "good" : "bad"));

var promB = promA.then(r => (r === "good" ? "ALL OK" : "NOTOK"));

var promC = statusProm.then(r => fetchThisAnotherThing());

上面出现的 promise 可以用下面这个流程图来表示:

29b737c5cd737031a8a944e3b2610480ca21e674

重点注意, promApromB 和 promC 虽然相关,但都是不同的 promise 实例。

我喜欢把这种 .then 链当做一个巨大的水暖系统,如果父节点出了故障,热水将无法流入到自节点中。例如,如果 promB 故障了,其他节点不受影响,但是如果 statusProm 出问题了,其他节点就会被影响,也就是被 rejected

3. 在任何情况下,Promise resolve/reject 状态都是一致的

这是 Promise 之所以好用的原因。简单理解,就是如果一个 promise 在多个地方使用,当它被 resolve 或者 reject 的时候,都会获得通知。

而且 promise 是无法被修改的,因此它可以随意传递。

function yourFunc() {
  const yourAwesomeProm = makeMeProm();

  // 无论坏叔叔如何消费你的 promise,你的 promise 都可以正常工作
  yourEvilUncle(yourAwesomeProm);

  return yourAwesomeProm.then(r => importantProcessing(r));
}

function yourEvilUncle(prom) {
  // 坏叔叔
  return prom.then(r => Promise.reject("destroy!!"));
}

Promise 的设计避免了恶意的破坏,如我所说:“没事,可以把 promise 随便扔!”

4. Promise 构造函数不是万金油

我发现有些工程师在任何地方都会使用 Promise 的 constructor,还认为这就是 promise 的使用方式。这是不对的,根本原因就是 constructor API 与原来 callback API 很像,老的习惯很难改。

如果你的代码中遍布 Promise constructor,你的做法就是错的!

如果你想更进一步,摆脱回调函数,你应该尽量减少 Promise 构造函数的使用。

Promise 构造函数正确的使用场景如下:

return new Promise((res, rej) => {
  fs.readFile("/etc/passwd", function(err, data) {
    if (err) return rej(err);
    return res(data);
  });
});

Promise constructor 只在将回调转成 promise 时使用。

看一个冗余的例子:

// 错误用法
return new Promise((res, rej) => {
    var fetchPromise = fetchSomeData(.....);
    fetchPromise
        .then(data => {
            res(data); // 错误的方式
        })
        .catch(err => rej(err))
})

// 正确用法

// 看上去对就是对的
return fetchSomeData(...);

在 Node.js 中,推荐使用 util.promisify。用来将回调 API 转成 promise 式的:

const {promisify} = require('util');
const fs = require('fs');

const readFileAsync = promisify(fs.readFile);

readFileAsync('myfile.txt', 'utf-8')
  .then(r => console.log(r))
  .catch(e => console.error(e));

5. 使用 Promise.resolve

JavaScript 提供了 Promise.resolve API,是产生 Promise 对象的一种快捷方式,这个 promise 对象是被 resolve 的。

var similarProm = new Promise(res => res(5));
// 相当于
var prom = Promise.resolve(5);

这有很多使用场景,我最喜欢的一个是,将一个同步的对象转成一个 promise:

// 将一个同步函数转成异步的
function foo() {
  return Promise.resolve(5);
}

也可以用来在不确定返回值是普通对象还是 promise 时,将返回值封装为 promise 对象:

function goodProm(maybePromise) {
  return Promise.resolve(maybePromise);
}

goodProm(5).then(console.log); // 5

// 这个 promise resolve 成 5
var sixPromise = fetchMeNumber(6); 

goodProm(sixPromise).then(console.log); // 6

// 5,注意,每层 promise 都被自动 unwrap 了
goodProm(Promise.resolve(Promise.resolve(5))).then(console.log);

6. 使用 Promise.reject

与 Promise.resolve 类似,它也是一种快捷写法

var rejProm = new Promise((res, reject) => reject(5));

rejProm.catch(e => console.log(e)) // 5

我最喜欢的 Promise.reject 的用法是,尽早地 reject:

function foo(myVal) {
    if (!mVal) {
        return Promise.reject(new Error('myVal is required'))
    }
    return new Promise((res, rej) => {
        // 这些将巨大的 callback 转成 promise
    })
}

在 .then 中使用 reject:

.then(val => {
  if (val != 5) {
    return Promise.reject('Not Good');
  }
})
.catch(e => console.log(e)) // Not Good

7. 使用 Promise.all

JavaScript 还提供了 Promise.all,但它不是什么快捷方式。

可以如下总结它的算法:

接受一个 promise 的数组
    等待所有这些 promise 完成
    返回一个新的 Promise,将所有的 resolve 结果放进一个数组里
    只要有一个  promise 失败/rejected,这个新的 promise 将会被 rejected

下例展示了所有 promise 都 resolve 的情况:

var prom1 = Promise.resolve(5);
var prom2 = fetchServerStatus(); // returns a promise of {statusCode: 200}

Proimise.all([prom1, prom2])
.then([val1, val2] => { // notice that it resolves into an Array
    console.log(val1); // 5
    console.log(val2.statusCode); // 200
})

下例展示有一个失败的情况:

var prom1 = Promise.reject(5);
var prom2 = fetchServerStatus(); // returns a promise of {statusCode: 200}

Proimise.all([prom1, prom2])
.then([val1, val2] => {
    console.log(val1); 
    console.log(val2.statusCode); 
})
.catch(e =>  console.log(e)) // 5, jumps directly to .catch

注意Promise.all 是一点不笨,只要有一个 promise 被 reject 了,它就直接 reject,不会等到其他 promise 完成。

8. 不要害怕 reject 或者不要在每一个 .then 后面使用 .catch

我们是不是常常感到有很多隐藏的错误没有被处理?

不用担心像下面这样写:

return fetchSomeData(...);

你可以在任何你想处理的地方解决或者延续 rejection。

处理掉 rejection

这很简单,在 .catch 回调中,无论你返回什么都会变成 resolve,除非你返回一个 Promise.reject,才会延续 rejection。

.then(() => 5.length) // <-- something wrong happenned here
.catch(e => {
        return 5;  // <-- making javascript great again
})
.then(r => {
    console.log(r); // 5
})
.catch(e => {
    console.error(e); // this function will never be called :)
})

reject rejection

reject rejection 的方法就是什么都不做。通常,父函数比起当前函数更擅长处理 rejection。

要记住一个要点,一旦你写了 .catch 就意味着 rejection 已经被处理了,这与同步的 try/catch 类似。

如果你确实想要阻断 rejection(我强烈不推荐这么做):

.then(() => 5.length) // <-- something wrong happenned here
.catch(e => {
  errorLogger(e); // do something impure
  return Promise.reject(e); // reject it, Yes you can do that!
})
.then(r => {
    console.log(r); // this .then (or any subsequent .then) will never be called as we rejected it above :)
})
.catch(e => {
    console.error(e); //<-- it becomes this catch's problem
})

.then(x,y) 和 then(x).catch(x)

.then 接受第二个回调参数来处理错误。虽然与 then(x).catch(x) 看一起类似,但却有所不同,不同点在于可捕获的错误。

下面例子很好地说了这个问题:

.then(function() {
   return Promise.reject(new Error('something wrong happened'));
}).catch(function(e) {
   console.error(e); // something wrong happened
});


.then(function() {
   return Promise.reject(new Error('something wrong happened'));
}, function(e) { // callback handles error coming from the chain above the current `.then`
    console.error(e); // no error logged
});

9. 避免 .then 嵌套

这个原则理解起来很简单,就是避免在 .then 里面继续使用 .then 或者 .catch。相信我,这绝对是可以避免的。

// 错误用法
request(opts)
.catch(err => {
  if (err.statusCode === 400) {
    return request(opts)
           .then(r => r.text())
           .catch(err2 => console.error(err2))
  }
})

// 正确用法
request(opts)
.catch(err => {
  if (err.statusCode === 400) {
    return request(opts);
  }
  return Promise.reject(err);
})
.then(r => r.text())
.catch(err => console.erro(err));

就算是如下这种情况,也可以使用 Promise.all 来解决:

.then(myVal => {
    const promA = foo(myVal);
    const promB = anotherPromMake(myVal);
    return promA
          .then(valA => {
              return promB.then(valB => hungryFunc(valA, valB)); // very hungry!
          })
})

可以像下面这样:

.then(myVal => {
    const promA = foo(myVal);
    const promB = anotherPromMake(myVal);
    return Promise.all([prom, anotherProm])
})
.then(([valA, valB]) => {   // putting ES6 destructing to good use
    console.log(valA, valB) // all the resolved values
    return hungryFunc(valA, valB)
})

好了,真心希望本文可以帮到你更好地理解 Promise!

原文发布时间:2018年03月

作者:

本文来源:前端外刊  如需转载请联系原作者

相关文章:

  • Linux ,强制更新只读文件,强制写入命令
  • 卸载pip工具
  • Ubuntu 12.04将默认集成Landscape管理套件【转】
  • 基础技能 | Git
  • SPP-net原理解读
  • [HDU3710]Battle over Cities
  • Vue学习笔记4
  • 你知道吗?一把能打开100000亿新兴市场的钥匙就攥着你手里!
  • Faiss教程:基础
  • 你的鞋都比你聪明
  • [CF226E]Noble Knight's Path
  • SQL Server内幕之预估与实际执行计划
  • [笔记] 四边形不等式
  • [js] 正则表达式
  • Windows 7安装超级终端连接COM口设备
  • Angular2开发踩坑系列-生产环境编译
  • JavaScript新鲜事·第5期
  • JavaScript异步流程控制的前世今生
  • JS函数式编程 数组部分风格 ES6版
  • Laravel 实践之路: 数据库迁移与数据填充
  • passportjs 源码分析
  • PHP 7 修改了什么呢 -- 2
  • php中curl和soap方式请求服务超时问题
  • Shell编程
  • Spark RDD学习: aggregate函数
  • SpiderData 2019年2月13日 DApp数据排行榜
  • Storybook 5.0正式发布:有史以来变化最大的版本\n
  • Yeoman_Bower_Grunt
  • 闭包--闭包作用之保存(一)
  • 从零开始的无人驾驶 1
  • 工程优化暨babel升级小记
  • 前端每日实战 2018 年 7 月份项目汇总(共 29 个项目)
  • 容器服务kubernetes弹性伸缩高级用法
  • 一起参Ember.js讨论、问答社区。
  • C# - 为值类型重定义相等性
  • hi-nginx-1.3.4编译安装
  • #中的引用型是什么意识_Java中四种引用有什么区别以及应用场景
  • (1) caustics\
  • (剑指Offer)面试题41:和为s的连续正数序列
  • (区间dp) (经典例题) 石子合并
  • (转)德国人的记事本
  • (转)关于pipe()的详细解析
  • .NET 8.0 发布到 IIS
  • .Net CF下精确的计时器
  • .NET Compact Framework 3.5 支持 WCF 的子集
  • .NET Core MongoDB数据仓储和工作单元模式封装
  • .Net Core 中间件验签
  • .net oracle 连接超时_Mysql连接数据库异常汇总【必收藏】
  • .NET Standard 的管理策略
  • .NET 中选择合适的文件打开模式(CreateNew, Create, Open, OpenOrCreate, Truncate, Append)
  • .NET牛人应该知道些什么(2):中级.NET开发人员
  • @CacheInvalidate(name = “xxx“, key = “#results.![a+b]“,multi = true)是什么意思
  • []利用定点式具实现:文件读取,完成不同进制之间的
  • [AIGC] Java 和 Kotlin 的区别
  • [android] 手机卫士黑名单功能(ListView优化)