专栏名称: 前端外刊评论
最新、最前沿的前端资讯,最有深入、最干前端相关的技术译文。
目录
相关文章推荐
商务河北  ·  经开区“美•强•优”三重奏 ·  13 小时前  
奇舞精选  ·  从 DeepSeek 看25年前端的一个小趋势 ·  昨天  
奇舞精选  ·  从 DeepSeek 看25年前端的一个小趋势 ·  昨天  
前端早读课  ·  【第3451期】前端 TypeError ... ·  2 天前  
51好读  ›  专栏  ›  前端外刊评论

Promise 使用技巧九则

前端外刊评论  · 公众号  · 前端  · 2018-03-05 08:10

正文

本文译自 9 Promising Promise Tips。

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

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

我必须大声喊出来:

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

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

  1. .then(r => {

  2.    // 这是一个 { statusCode: 200 } promise

  3.    return serverStatusPromise(r);

  4. })

  5. .then(resp => {

  6.    // 200;注意上面的 promise 被自动 unwrap 了

  7.    console.log(resp.statusCode);

  8. })

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

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

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

  1. var statusProm = fetchServerStatus();

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

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

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

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

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

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

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

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

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

  1. function yourFunc() {

  2.  const yourAwesomeProm = makeMeProm();

  3.  // 无论坏叔叔如何消费你的 promise,你的 promise 都可以正常工作

  4.  yourEvilUncle(yourAwesomeProm);

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

  6. }

  7. function yourEvilUncle(prom) {

  8.  // 坏叔叔

  9.  return prom.then(r => Promise.reject("destroy!!"));

  10. }

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

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

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

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

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

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

  1. return new Promise((res, rej) => {

  2.  fs.readFile("/etc/passwd", function(err, data) {

  3.    if (err) return rej(err);

  4.    return res(data);

  5.  });

  6. });

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

看一个冗余的例子:

  1. // 错误用法

  2. return new Promise((res, rej) => {

  3.    var fetchPromise = fetchSomeData(.....);

  4.    fetchPromise

  5.        .then(data => {

  6.            res(data); // 错误的方式

  7.        })

  8.        .catch(err => rej(err))

  9. })

  10. // 正确用法

  11. // 看上去对就是对的

  12. return fetchSomeData(...);

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

  1. const {promisify} = require('util');

  2. const fs = require('fs');

  3. const readFileAsync = promisify(fs.readFile);

  4. readFileAsync('myfile.txt', 'utf-8')

  5.  .then(r => console.log(r))

  6.  .catch(e => console.error(e));

5. 使用 Promise.resolve

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

  1. var similarProm = new Promise(res => res (5));

  2. // 相当于

  3. var prom = Promise.resolve(5);

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

  1. // 将一个同步函数转成异步的

  2. function foo() {

  3.  return Promise.resolve(5);

  4. }

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

  1. function goodProm(maybePromise) {

  2.  return Promise.resolve(maybePromise);

  3. }

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

  5. // 这个 promise resolve 成 5

  6. var sixPromise = fetchMeNumber(6);

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

  8. // 5,注意,每层 promise 都被自动 unwrap 了

  9. goodProm(Promise.resolve(Promise.resolve(5))).then(console.log);

6. 使用 Promise.reject

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

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

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

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

  1. function foo(myVal) {

  2.    if (!mVal) {

  3.        return Promise.reject(new Error('myVal is required'))

  4.    }

  5.    return new Promise((res, rej) => {

  6.        // 这些将巨大的 callback 转成 promise

  7.    })

  8. }

. then 中使用 reject:

  1. .then(val => {

  2.  if (val != 5) {

  3.    return Promise.reject('Not Good');

  4.  }

  5. })

  6. .catch(e => console.log(e)) // Not Good

7. 使用 Promise.all

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

可以如下总结它的算法:

  1. 接受一个 promise 的数组

  2.    等待所有这些 promise 完成

  3.    返回一个新的 Promise,将所有的 resolve 结果放进一个数组里

  4.    只要有一个  promise 失败/rejected,这个新的 promise 将会被 rejected

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

  1. var prom1 = Promise.resolve(5);

  2. var prom2 = fetchServerStatus(); // returns a promise of {statusCode: 200}

  3. Proimise.all([ prom1, prom2])

  4. .then([val1, val2] => { // notice that it resolves into an Array

  5.    console.log(val1); // 5

  6.    console.log(val2.statusCode); // 200

  7. })

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

  1. var prom1 = Promise.reject(5);

  2. var prom2 = fetchServerStatus(); // returns a promise of {statusCode: 200}

  3. Proimise.all([prom1, prom2])

  4. .then([val1, val2] => {

  5.    console.log(val1 );

  6.    console.log(val2.statusCode);

  7. })

  8. .catch(e =>  console.log(e)) // 5, jumps directly to .catch

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

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

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

不用担心像下面这样写:

  1. return fetchSomeData(...);

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

处理掉 rejection

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

  1. .then(() => 5.length) //

  2. .catch(e => {

  3.         return 5;  //

  4. })

  5. .then(r => {

  6.    console.log(r); // 5

  7. })

  8. .catch(e => {

  9.    console.error(e); // this function will never be called :)

  10. })

reject rejection

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

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

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

  1. .then(() => 5.length) //

  2. .catch(e => {

  3.  errorLogger( e); // do something impure

  4.  return Promise.reject(e); // reject it, Yes you can do that!

  5. })

  6. .then(r => {

  7.    console.log(r); // this .then (or any subsequent .then) will never be called as we rejected it above :)

  8. })

  9. .catch(e => {

  10.    console.error(e); //

  11. })

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

. then 接受第二个回调参数来处理错误。虽然与 then ( x ).







请到「今天看啥」查看全文