共计 9698 个字符,预计需要花费 25 分钟才能阅读完成。
作者:Adrian Mejia
译者:前端小智
起源:adrianmjia
点赞再看,养成习惯
本文
GitHub
https://github.com/qq44924588… 上曾经收录,更多往期高赞文章的分类,也整顿了很多我的文档,和教程材料。欢送 Star 和欠缺,大家面试能够参照考点温习,心愿咱们一起有点货色。
这篇文章算是 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! ????
还是输入:
time is 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#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)
输入如下:
在这种状况下,能够看到 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.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'))
如果你运行该代码示例,你会留神到只有 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(`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。