You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
在此示例中,主要功能是将美元转换为欧元,我们有两个独立的 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);
/**
* 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));
}
这篇文章算是 JavaScript Promises 比较全面的教程,该文介绍了必要的方法,例如
then
,catch
和finally
。 此外,还包括处理更复杂的情况,例如与Promise.all
并行执行Promise
,通过Promise.race
来处理请求超时的情况,Promise 链以及一些最佳实践和常见的陷阱。1.JavaScript Promises
Promise 是一个允许我们处理异步操作的对象,它是 es5 早期回调的替代方法。
与回调相比,Promise 具有许多优点,例如:
* 更好的流程控制,可以让异步并行或串行执行。
回调更容易形成深度嵌套的结构(也称为
回调地狱
)。 如下所示:如果将这些函数转换为
Promise
,则可以将它们链接起来以生成更可维护的代码。 像这样:在上面的示例中,Promise 对象公开了
.then
和.catch
方法,我们稍后将探讨这些方法。1.1 如何将现有的回调 API 转换为 Promise?
我们可以使用 Promise 构造函数将回调转换为 Promise。
Promise 构造函数接受一个回调,带有两个参数
resolve
和reject
。构造函数立即返回一个对象,即
Promise
实例。 当在 promise 实例中使用.then
方法时,可以在Promise “完成” 时得到通知。 让我们来看一个例子。Promise 仅仅只是回调?
并不是。承诺不仅仅是回调,但它们确实对
.then
和.catch
方法使用了异步回调。 Promise 是回调之上的抽象,我们可以链接多个异步操作并更优雅地处理错误。来看看它的实际效果。Promise 反面模式(Promises 地狱)
不要将上面的回调转成下面的 Promise 形式:
上面的转成,也形成了 Promise 地狱,千万不要这么转。相反,下面这样做会好点:
超时
你认为以下程序的输出的是什么?
是输出:
还是输出:
是后者,因为当一个Promise
resolved
后,它就不能再被rejected
。一旦你调用一种方法(
resolve
或reject
),另一种方法就会失效,因为promise
处于稳定状态。 让我们探索一个promise
的所有不同状态。1.2 Promise 状态
Promise 可以分为四个状态:
.then
回调,例如.then(onSuccess)
。.catch
或.then
的第二个参数(如果有)。 例如.catch(onError)
或.then(..., onError)
。.finally
方法被调用。1.3 Promise 实例方法
Promise API 公开了三个主要方法:
then
,catch
和finally
。 我们逐一配合事例探讨一下。Promise then
then
方法可以让异步操作成功或失败时得到通知。 它包含两个参数,一个用于成功执行,另一个则在发生错误时使用。你还可以使用
catch
来处理错误:Promise 链
then
返回一个新的 Promise ,这样就可以将多个Promise 链接在一起。就像下面的例子一样:Promise.resolve
立即将Promise 视为成功。 因此,以下所有内容都将被调用。 输出将是Promise catch
Promise
.catch
方法将函数作为参数处理错误。 如果没有出错,则永远不会调用catch
方法。假设我们有以下承诺:
1
秒后解析或拒绝并打印出它们的字母。请注意,
c
使用reject('Oops!')
模拟了拒绝。输出如下:
在这种情况下,可以看到
a
,b
和c
上的错误消息。我们可以使用
then
函数的第二个参数来处理错误。 但是,请注意,catch
将不再执行。由于我们正在处理
.then(..., onError)
部分的错误,因此未调用catch
。d
不会被调用。 如果要忽略错误并继续执行Promise链,可以在c
上添加一个catch
。 像这样:当然,这种过早的捕获错误是不太好的,因为容易在调试过程中忽略一些潜在的问题。
Promise finally
finally
方法只在 Promise 状态是settled
时才会调用。如果你希望一段代码即使出现错误始终都需要执行,那么可以在
.catch
之后使用.then
。或者可以使用
.finally
关键字:1.4 Promise 类方法
我们可以直接使用
Promise
对象中四种静态方法。Promise.resolve 和 Promise.reject
这两个是帮助函数,可以让 Promise 立即解决或拒绝。可以传递一个参数,作为下次
.then
的接收:上面会输出
Yay!!!
使用
Promise.all
并行执行多个 Promise通常,Promise 是一个接一个地依次执行的,但是你也可以并行使用它们。
假设是从两个不同的api中轮询数据。如果它们不相关,我们可以使用
Promise.all()
同时触发这两个请求。在此示例中,主要功能是将美元转换为欧元,我们有两个独立的 API 调用。 一种用于
BTC/USD
,另一种用于获得EUR/USD
。 如你所料,两个 API 调用都可以并行调用。 但是,我们需要一种方法来知道何时同时完成最终价格的计算。 我们可以使用Promise.all
,它通常在启动多个异步任务并发运行并为其结果创建承诺之后使用,以便人们可以等待所有任务完成。如你所见,
Promise.all
接受了一系列的 Promises。 当两个请求的请求都完成后,我们就可以计算价格了。我们再举一个例子:
解决这些 Promise 要花多长时间? 5秒? 1秒? 还是2秒?
这个留给你们自己验证咯。
Promise race
Promise.race(iterable)
方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。输出是什么?
输出
b
。使用Promise.race
,最先执行完成就会结果最后的返回结果。你可能会问:
Promise.race
的用途是什么?我没胡经常使用它。但是,在某些情况下,它可以派上用场,比如计时请求或批量处理请求数组。
如果请求足够快,那么就会得到请求的结果。
1.5 Promise 常见问题
串行执行 promise 并传递参数
这次,我们将对Node的
fs
使用promises API,并将两个文件连接起来:在此示例中,我们读取文件1并将其写入
output
文件。 稍后,我们读取文件2并将其再次附加到output
文件。 如你所见,writeFile
promise返回文件的内容,你可以在下一个then
子句中使用它。如何链接多个条件承诺?
你可能想要跳过 Promise 链上的特定步骤。有两种方法可以做到这一点。
如果你运行该代码示例,你会注意到只有
a
和d
被按预期执行。另一种方法是创建一个链,然后仅在以下情况下添加它们:
如何限制并行 Promise?
要做到这一点,我们需要以某种方式限制
Promise.all
。假设你有许多并发请求要执行。 如果使用
Promise.all
是不好的(特别是在API受到速率限制时)。 因此,我们需要一个方法来限制 Promise 个数, 我们称其为promiseAllThrottled
。输出应该是这样的:
以上代码将并发限制为并行执行的
3
个任务。实现
promiseAllThrottled
一种方法是使用Promise.race
来限制给定时间的活动任务数量。promiseAllThrottled
一对一地处理 Promises 。 它执行Promises
并将其添加到队列中。 如果队列小于并发限制,它将继续添加到队列中。 达到限制后,我们使用Promise.race
等待一个承诺完成,因此可以将其替换为新的承诺。 这里的技巧是,promise 自动完成后会自动从队列中删除。 另外,我们使用race
来检测promise 何时完成,并添加新的 promise 。人才们的 【三连】 就是小智不断分享的最大动力,如果本篇博客有任何错误和建议,欢迎人才们留言,最后,谢谢大家的观看。
代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。
原文:https://adrianmejia.com/promises-tutorial-concurrency-in-javascript-node/
文章每周持续更新,可以微信搜索 【大迁世界 】 第一时间阅读,回复 【福利】 有多份前端视频等着你,本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,欢迎Star。
The text was updated successfully, but these errors were encountered: