这首诗称得上 JavaScript Promises 较为全面性的讲义,该书如是说了必要性的方式,比如 then,catch和finally。除此之外,还包括处置更繁杂的情形,比如与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 构造表达式接受两个反弹,带有两个参数resolve和reject。
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!还是输出:
timeis up ⏰是后者,因为当两个Promise resolved 后,它就不能再被rejected。
一旦你调用一种方式(resolve 或reject),另一种方式就会失效,因为 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 公开了三个主要方式:then,catch和finally。他们逐一配合事例探讨一下。
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#3Promise 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)输出如下表所示:
在这种情形下,能看到a,b和c上的错误消息。
他们能使用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)部分的错误,因此未调用catch。d不会被调用。假如要忽略错误并继续继续执行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.allPromise.rejectPromise.resolvePromise.racePromise.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]) => { constbyCoinbaseBtc =d => d.exchange_id === coinbase-pro && d.pair === BTC/USD; constcoinbaseBtc = bitcoinMarkets.data.find(byCoinbaseBtc)const coinbaseBtcInUsd = coinbaseBtc.quotes.USD.price; constrate = 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 = () => newPromise((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))假如你运行该标识符示例,你会注意到只有a和d被按预期继续执行。
另一种方式是创建两个链,然后仅在以下情形下添加它:
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(`execing 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 queuequeue.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 。
人才们的 【三连】 就是小智不断分享的最大动力,假如本篇博客有任何错误和建议,欢迎人才们留言,最后,谢谢大家的观看。
作者:Adrian Mejia 译者:前端小智
原文:
https://adrianmejia.com/promises-tutorial-concurrency-in-javascript-node/