关于javascript:初学者应该看的JavaScript-Promise-完整指南

36次阅读

共计 9698 个字符,预计需要花费 25 分钟才能阅读完成。

作者:Adrian Mejia
译者:前端小智
起源:adrianmjia

点赞再看,养成习惯

本文 GitHub https://github.com/qq44924588… 上曾经收录,更多往期高赞文章的分类,也整顿了很多我的文档,和教程材料。欢送 Star 和欠缺,大家面试能够参照考点温习,心愿咱们一起有点货色。

这篇文章算是 JavaScript Promises 比拟全面的教程,该文介绍了必要的办法,例如 thencatchfinally。此外,还包含解决更简单的状况,例如与Promise.all 并行执行Promise,通过Promise.race 来解决申请超时的状况,Promise 链以及一些最佳实际和常见的陷阱。

1.JavaScript Promises

Promise 是一个容许咱们解决异步操作的对象,它是 es5 晚期回调的代替办法。

与回调相比,Promise 具备许多长处,例如:

  • 让异步代码更易于浏览。
  • 提供组合错误处理。

* 更好的流程管制,能够让异步并行或串行执行。

回调更容易造成深度嵌套的构造(也称为 回调天堂)。如下所示:

a(() => {b(() => {c(() => {d(() => {// and so on ...});
    });
  });
});

如果将这些函数转换为 Promise,则能够将它们链接起来以生成更可保护的代码。像这样:

Promise.resolve()
  .then(a)
  .then(b)
  .then(c)
  .then(d)
  .catch(console.error);

在下面的示例中,Promise 对象公开了 .then.catch办法,咱们稍后将探讨这些办法。

1.1 如何将现有的回调 API 转换为 Promise?

咱们能够应用 Promise 构造函数将回调转换为 Promise。

Promise 构造函数承受一个回调,带有两个参数 resolvereject

  • Resolve:是在异步操作实现时应调用的回调。
  • Reject:是产生谬误时要调用的回调函数。

构造函数立刻返回一个对象,即 Promise 实例。当在 promise 实例中应用 .then 办法时,能够在 Promise“实现”时失去告诉。让咱们来看一个例子。

Promise 仅仅只是回调?

并不是。承诺不仅仅是回调,但它们的确对 .then.catch办法应用了异步回调。Promise 是回调之上的形象,咱们能够链接多个异步操作并更优雅地处理错误。来看看它的实际效果。

Promise 背面模式(Promises 天堂)

a(() => {b(() => {c(() => {d(() => {// and so on ...});
    });
  });
});

不要将下面的回调转成上面的 Promise 模式:

a().then(() => {return b().then(() => {return c().then(() => {return d().then(() =>{// ⚠️ Please never ever do to this! ⚠️});
    });
  });
});

下面的转成,也造成了 Promise 天堂,千万不要这么转。相同,上面这样做会好点:

a()
  .then(b)
  .then(c)
  .then(d)

超时

你认为以下程序的输入的是什么?

const promise = new Promise((resolve, reject) => {setTimeout(() => {resolve('time is up ⏰');
  }, 1e3);

  setTimeout(() => {reject('Oops ????');
  }, 2e3);
});

promise
  .then(console.log)
  .catch(console.error);

是输入:

time is up ⏰
Oops! ????

还是输入:

time is up ⏰

是后者,因为当一个 Promise resolved 后,它就不能再被rejected

一旦你调用一种办法(resolvereject),另一种办法就会生效,因为 promise 处于稳固状态。让咱们摸索一个 promise 的所有不同状态。

1.2 Promise 状态

Promise 能够分为四个状态:

  • ⏳ Pending:初始状态,异步操作仍在进行中。
  • ✅ Fulfilled:操作胜利,它调用 .then 回调,例如.then(onSuccess)
  • ⛔️ Rejected: 操作失败,它调用 .catch.then的第二个参数(如果有)。例如 .catch(onError).then(..., onError)
  • ???? Settled:这是 promise 的最终状态。promise 曾经死亡了,没有别的方法能够解决或回绝了。.finally办法被调用。

1.3 Promise 实例办法

Promise API 公开了三个次要办法:thencatchfinally。咱们逐个配合事例探讨一下。

Promise then

then办法能够让异步操作胜利或失败时失去告诉。它蕴含两个参数,一个用于胜利执行,另一个则在产生谬误时应用。

promise.then(onSuccess, onError);

你还能够应用 catch 来处理错误:

promise.then(onSuccess).catch(onError);

Promise 链

then 返回一个新的 Promise,这样就能够将多个 Promise 链接在一起。就像上面的例子一样:

Promise.resolve()
  .then(() => console.log('then#1'))
  .then(() => console.log('then#2'))
  .then(() => console.log('then#3'));

Promise.resolve立刻将 Promise 视为胜利。因而,以下所有内容都将被调用。输入将是

then#1
then#2
then#3

Promise catch

Promise .catch办法将函数作为参数处理错误。如果没有出错,则永远不会调用 catch 办法。

假如咱们有以下承诺:1秒后解析或回绝并打印出它们的字母。

const a = () => new Promise((resolve) => setTimeout(() => { console.log('a'), resolve()}, 1e3));
const b = () => new Promise((resolve) => setTimeout(() => { console.log('b'), resolve()}, 1e3));
const c = () => new Promise((resolve, reject) => setTimeout(() => { console.log('c'), reject('Oops!') }, 1e3));
const d = () => new Promise((resolve) => setTimeout(() => { console.log('d'), resolve()}, 1e3));

请留神,c应用 reject('Oops!') 模仿了回绝。

Promise.resolve()
  .then(a)
  .then(b)
  .then(c)
  .then(d)
  .catch(console.error)

输入如下:

在这种状况下,能够看到 abc上的谬误音讯。

咱们能够应用 then 函数的第二个参数来处理错误。然而,请留神,catch将不再执行。

Promise.resolve()
  .then(a)
  .then(b)
  .then(c)
  .then(d, () => console.log('c errored out but no big deal'))
  .catch(console.error)

因为咱们正在解决 .then(..., onError)局部的谬误,因而未调用 catchd 不会被调用。如果要疏忽谬误并继续执行 Promise 链,能够在 c 上增加一个catch。像这样:

Promise.resolve()
  .then(a)
  .then(b)
  .then(() => c().catch(() => console.log('error ignored')))
  .then(d)
  .catch(console.error)

当然,这种过早的捕捉谬误是不太好的,因为容易在调试过程中疏忽一些潜在的问题。

Promise finally

finally办法只在 Promise 状态是 settled 时才会调用。

如果你心愿一段代码即便呈现谬误始终都须要执行,那么能够在 .catch 之后应用.then

Promise.resolve()
  .then(a)
  .then(b)
  .then(c)
  .then(d)
  .catch(console.error)
  .then(() => console.log('always called'));

或者能够应用 .finally 关键字:

Promise.resolve()
  .then(a)
  .then(b)
  .then(c)
  .then(d)
  .catch(console.error)
  .finally(() => console.log('always called'));

1.4 Promise 类办法

咱们能够间接应用 Promise 对象中四种静态方法。

  • Promise.all
  • Promise.reject
  • Promise.resolve
  • Promise.race

Promise.resolve 和 Promise.reject

这两个是帮忙函数,能够让 Promise 立刻解决或回绝。能够传递一个参数,作为下次 .then 的接管:

Promise.resolve('Yay!!!')
  .then(console.log)
  .catch(console.error)

下面会输入 Yay!!!

Promise.reject('Oops ????')
  .then(console.log)
  .catch(console.error)

应用 Promise.all 并行执行多个 Promise

通常,Promise 是一个接一个地顺次执行的,然而你也能够并行应用它们。

假如是从两个不同的 api 中轮询数据。如果它们不相干,咱们能够应用 Promise.all() 同时触发这两个申请。

在此示例中,次要性能是将美元转换为欧元,咱们有两个独立的 API 调用。一种用于BTC/USD,另一种用于取得EUR/USD。如你所料,两个 API 调用都能够并行调用。然而,咱们须要一种办法来晓得何时同时实现最终价格的计算。咱们能够应用Promise.all,它通常在启动多个异步工作并发运行并为其后果创立承诺之后应用,以便人们能够期待所有工作实现。

const axios = require('axios');

const bitcoinPromise = axios.get('https://api.coinpaprika.com/v1/coins/btc-bitcoin/markets');
const dollarPromise = axios.get('https://api.exchangeratesapi.io/latest?base=USD');
const currency = 'EUR';

// Get the price of bitcoins on
Promise.all([bitcoinPromise, dollarPromise])
  .then(([bitcoinMarkets, dollarExchanges]) => {
    const byCoinbaseBtc = d => d.exchange_id === 'coinbase-pro' && d.pair === 'BTC/USD';
    const coinbaseBtc = bitcoinMarkets.data.find(byCoinbaseBtc)
    const coinbaseBtcInUsd = coinbaseBtc.quotes.USD.price;
    const rate = dollarExchanges.data.rates[currency];
    return rate * coinbaseBtcInUsd;
  })
  .then(price => console.log(`The Bitcoin in ${currency} is ${price.toLocaleString()}`))
  .catch(console.log);

如你所见,Promise.all承受了一系列的 Promises。当两个申请的申请都实现后,咱们就能够计算价格了。

咱们再举一个例子:

const a = () => new Promise((resolve) => setTimeout(() => resolve('a'), 2000));
const b = () => new Promise((resolve) => setTimeout(() => resolve('b'), 1000));
const c = () => new Promise((resolve) => setTimeout(() => resolve('c'), 1000));
const d = () => new Promise((resolve) => setTimeout(() => resolve('d'), 1000));

console.time('promise.all');
Promise.all([a(), b(), c(), d()])
  .then(results => console.log(`Done! ${results}`))
  .catch(console.error)
  .finally(() => console.timeEnd('promise.all'));

解决这些 Promise 要花多长时间?5 秒?1 秒?还是 2 秒?

这个留给你们本人验证咯。

Promise race

Promise.race(iterable) 办法返回一个 promise,一旦迭代器中的某个 promise 解决或回绝,返回的 promise 就会解决或回绝。

const a = () => new Promise((resolve) => setTimeout(() => resolve('a'), 2000));
const b = () => new Promise((resolve) => setTimeout(() => resolve('b'), 1000));
const c = () => new Promise((resolve) => setTimeout(() => resolve('c'), 1000));
const d = () => new Promise((resolve) => setTimeout(() => resolve('d'), 1000));

console.time('promise.race');
Promise.race([a(), b(), c(), d()])
  .then(results => console.log(`Done! ${results}`))
  .catch(console.error)
  .finally(() => console.timeEnd('promise.race'));

输入是什么?

输入 b。应用 Promise.race,最先执行实现就会后果最初的返回后果。

你可能会问:Promise.race的用处是什么?

我没胡常常应用它。然而,在某些状况下,它能够派上用场,比方计时申请或批量解决申请数组。

Promise.race([fetch('http://slowwly.robertomurray.co.uk/delay/3000/url/https://api.jsonbin.io/b/5d1fb4dd138da811182c69af'),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error('request timeout')), 1000))
])
.then(console.log)
.catch(console.error);

如果申请足够快,那么就会失去申请的后果。

1.5 Promise 常见问题

串行执行 promise 并传递参数

这次,咱们将对 Node 的 fs 应用 promises API,并将两个文件连接起来:

const fs = require('fs').promises; // requires node v8+

fs.readFile('file.txt', 'utf8')
  .then(content1 => fs.writeFile('output.txt', content1))
  .then(() => fs.readFile('file2.txt', 'utf8'))
  .then(content2 => fs.writeFile('output.txt', content2, { flag: 'a+'}))
  .catch(error => console.log(error));

在此示例中,咱们读取文件 1 并将其写入 output 文件。稍后,咱们读取文件 2 并将其再次附加到output 文件。如你所见,writeFile promise 返回文件的内容,你能够在下一个 then 子句中应用它。

如何链接多个条件承诺?

你可能想要跳过 Promise 链上的特定步骤。有两种办法能够做到这一点。

const a = () => new Promise((resolve) => setTimeout(() => { console.log('a'), resolve()}, 1e3));
const b = () => new Promise((resolve) => setTimeout(() => { console.log('b'), resolve()}, 2e3));
const c = () => new Promise((resolve) => setTimeout(() => { console.log('c'), resolve()}, 3e3));
const d = () => new Promise((resolve) => setTimeout(() => { console.log('d'), resolve()}, 4e3));

const shouldExecA = true;
const shouldExecB = false;
const shouldExecC = false;
const shouldExecD = true;

Promise.resolve()
  .then(() => shouldExecA && a())
  .then(() => shouldExecB && b())
  .then(() => shouldExecC && c())
  .then(() => shouldExecD && d())
  .then(() => console.log('done'))

如果你运行该代码示例,你会留神到只有 ad被按预期执行。

另一种办法是创立一个链,而后仅在以下状况下增加它们:

const chain = Promise.resolve();

if (shouldExecA) chain = chain.then(a);
if (shouldExecB) chain = chain.then(b);
if (shouldExecC) chain = chain.then(c);
if (shouldExecD) chain = chain.then(d);

chain
  .then(() => console.log('done'));

如何限度并行 Promise?

要做到这一点,咱们须要以某种形式限度Promise.all

假如你有许多并发申请要执行。如果应用 Promise.all 是不好的(特地是在 API 受到速率限度时)。因而,咱们须要一个办法来限度 Promise 个数,咱们称其为promiseAllThrottled

// simulate 10 async tasks that takes 5 seconds to complete.
const requests = Array(10)
  .fill()
  .map((_, i) => () => new Promise((resolve => setTimeout(() => {console.log(`exec'ing task #${i}`), resolve(`task #${i}`); }, 5000))));

promiseAllThrottled(requests, { concurrency: 3})
  .then(console.log)
  .catch(error => console.error('Oops something went wrong', error));

输入应该是这样的:

以上代码将并发限度为并行执行的 3 个工作。

实现 promiseAllThrottled 一种办法是应用Promise.race 来限度给定工夫的流动工作数量。

/**
 * Similar to Promise.all but a concurrency limit
 *
 * @param {Array} iterable Array of functions that returns a promise
 * @param {Object} concurrency max number of parallel promises running
 */
function promiseAllThrottled(iterable, { concurrency = 3} = {}) {const promises = [];

  function enqueue(current = 0, queue = []) {
    // return if done
    if (current === iterable.length) {return Promise.resolve(); }
    // take one promise from collection
    const promise = iterable[current];
    const activatedPromise = promise();
    // add promise to the final result array
    promises.push(activatedPromise);
    // add current activated promise to queue and remove it when done
    const autoRemovePromise = activatedPromise.then(() => {
      // remove promise from the queue when done
      return queue.splice(queue.indexOf(autoRemovePromise), 1);
    });
    // add promise to the queue
    queue.push(autoRemovePromise);

    // if queue length >= concurrency, wait for one promise to finish before adding more.
    const readyForMore = queue.length < concurrency ? Promise.resolve() : Promise.race(queue);
    return readyForMore.then(() => enqueue(current + 1, queue));
  }

  return enqueue()
    .then(() => Promise.all(promises));
}

promiseAllThrottled一对一地解决 Promises。它执行 Promises 并将其增加到队列中。如果队列小于并发限度,它将持续增加到队列中。达到限度后,咱们应用 Promise.race 期待一个承诺实现,因而能够将其替换为新的承诺。这里的技巧是,promise 主动实现后会主动从队列中删除。另外,咱们应用 race 来检测 promise 何时实现,并增加新的 promise。

人才们的 【三连】 就是小智一直分享的最大能源,如果本篇博客有任何谬误和倡议,欢送人才们留言,最初,谢谢大家的观看。


代码部署后可能存在的 BUG 没法实时晓得,预先为了解决这些 BUG,花了大量的工夫进行 log 调试,这边顺便给大家举荐一个好用的 BUG 监控工具 Fundebug。

原文:https://adrianmejia.com/promi…

交换

文章每周继续更新,能够微信搜寻 【大迁世界】 第一工夫浏览,回复 【福利】 有多份前端视频等着你,本文 GitHub https://github.com/qq449245884/xiaozhi 曾经收录,欢送 Star。

正文完
 0