实现promise.all方法
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实现promise.all方法相关的知识,希望对你有一定的参考价值。
参考技术A1- Promise.all 的用法
逆向的去实现功能,最关键的前提是准确了解API,输入、输出、和注意事项。
这里直接引用MDN:
Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败的原因是第一个失败 promise 的结果。
MDN后面也给出了详细说明:
此方法在集合多个promise的返回结果时很有用。
完成(Fulfillment):
如果传入的可迭代对象为空,Promise.all会同步地返回一个已完成(resolved)状态的promise。
如果所有传入的promise都变为完成状态,或者传入的可迭代对象内没有promise,Promise.all返回的promise异步地变为完成。
在任何情况下,Promise.all返回的promise的完成状态的结果都是一个数组,它包含所有的传入迭代参数对象的值(也包括非promise值)。
失败/拒绝(Rejection):
如果传入的promise中有一个失败(rejected),Promise.all异步地将失败的那个结果给失败状态的回调函数,而不管其它promise是否完成。
个人感觉MDN解释的比较清楚了,还是云里雾里的话,可以反复细品一下上面的说明。或者结合下面的代码去理解。
2 - 手动实现Promise.all
面试美团的时候,面试官看我写不出来,就说“既然你知道了输入和输出是什么,应该能写出来了....”。
面试官其实不是在鄙视我“我不行”,而是在试图引导我的思路,只是当时自己编程思路太差,最后还是没写出来。
但是面试官的提示,确实是一个很好的思考思路。 先不管完整的Promise.all代码是什么样子,甚至包括优化啥的。先想想"Promise.all(iterable) 方法返回一个 Promise实例",就这么简单的一句话怎么写呢?
function myPromiseAll(arr) // 参数是一个iterable对象,一般是数组// 返回一个Promise实例
return new Promise((resolve, reject) =>
resolve("面试官让我写一个Promise.all");
// 或者
// reject("我太笨了,写不出来");
);
let pResult = myPromiseAll([]); // 先不要去想数组有没有元素
pResult.then(value=>
console.log(value); // 输出: 面试官让我写一个Promise.all
, err=>
console.log(err);
)
好了,如过看懂了,那么最重要的一步就完成了。是不是很简单。
接下来,只要根据MDN的说明,一步步完善内部函数的功能就行了。
我们先从“完成”情况下手:
完成(Fulfillment):
A. 如果传入的可迭代对象为空,Promise.all会同步地返回一个已完成(resolved)状态的promise。
B. 如果所有传入的promise都变为完成状态,或者传入的可迭代对象内没有promise,Promise.all返回的promise异步地变为完成。
C. 在任何情况下,Promise.all返回的promise的完成状态的结果都是一个数组,它包含所有的传入迭代参数对象的值(也包括非promise值)。
请先看C,在完成情况下,会始终返回一个数组.
function myPromiseAll(arr)// 定义一个数组
let result = [];
return new Promise((resolve, reject) =>
// 现在只考虑 “在完成情况下” ,会返回一个数组
resolve(result);
);
let pResult = myPromiseAll([]);
pResult.then(value=>
console.log(pResult); // 输出 Promise <state>: "fulfilled", <value>: []
console.log(value); // 输出:[]
)
那么下面来实现B,B里有分两种情况:
元素是Promise实例
元素不是Promise实例
那先考虑元素不是Promise实例,从简单的开始
function myPromiseAll(arr)let result = [];
return new Promise((resolve, reject) =>
for(let i = 0; i < arr.length; i++)
result.push(arr[i]);
resolve(result);
);
let pResult = myPromiseAll([1,2,3]); // 元素不是Promise实例
pResult.then(value=>
console.log(pResult); // 输出: Promise <state>: "fulfilled", <value>: (3) […]
console.log(value); // 输出: Array(3) [ 1, 2, 3 ]
)
最难的来了,元素都是Promise实例呢?
别慌,先写顶层设计,再想细节(自上向下编程)
let result = [];
return new Promise((resolve, reject) =>
for(let i = 0; i < arr.length; i++)
if(/*如果是Promise实例*/)
else
result.push(arr[i]);
// 先想想,resolve放在这里,对不对?
resolve(result);
);
继续完善
function myPromiseAll(arr)let result = [];
return new Promise((resolve, reject) =>
// 数组为空,直接resolve了
if(arr.length == 0)
resolve(result);
for(let i = 0; i < arr.length; i++)
if(arr[i].then) // 若元素是Promise实例,则会有then函数,这里只是简单的作为判断标准
// 元素是Promise
arr[i].then(value =>
console.log(value);
result.push(value);
// 想一想什么时候resolve呢?--- 所有Promise实例都完成了
if(result.length == arr.length)
console.log("所有都完成了")
resolve(result);
)
else
result.push(arr[i]);
// 这段代码跟上面重复,想想,能不能提取放到外面,会出现什么情况呢?
if(result.length == arr.length)
resolve(result);
);
let p1 = new Promise((resolve, reject)=>
setTimeout(resolve, 2000, "P1 resolved");
)
let p2 = new Promise((resolve, reject)=>
setTimeout(resolve, 3000, "P2 resolved");
)
let p3 = new Promise((resolve, reject)=>
setTimeout(resolve, 4000, "P3 resolved");
)
let pResult = myPromiseAll([p1,p2,p3]);
pResult.then(value=>
console.log(pResult);
console.log(value);
)
// 输出
// P1 resolved
// P2 resolved
// P3 resolved
// 所有都完成了
// Promise <state>: "fulfilled", <value>: (3) […]
// Array(3) [ "P1 resolved", "P2 resolved", "P3 resolved" ]
完成情况写完了,还剩失败情况:
如果传入的 promise 中有一个失败(rejected),Promise.all 异步地将失败的那个结果给失败状态的回调函数,而不管其它 promise 是否完成。
function myPromiseAll(arr)let result = [];
return new Promise((resolve, reject) =>
// 如果数组为空,直接返回空数组
if(arr.length == 0)
resolve(result);
for(let i = 0; i < arr.length; i++)
if(arr[i].then) // 若元素是Promise实例,则会有then函数,这里只是简单的作为判断标准
// 元素是Promise
arr[i].then(value =>
console.log(value);
result.push(value);
// 想一想什么时候resolve呢?
if(result.length == arr.length)
console.log("所有都成功了")
resolve(result);
, err =>
console.log("很不幸,其中一个失败了");
// 注意到没, 这里没有像上面的判断 result.length == arr.length, 为什么?
// 只要碰到 resolve 或 reject ,就结束了
reject(err);
)
else
result.push(arr[i]);
// 这段代码跟上面重复,想想,能不能提取放到外面,会出现什么情况呢?
if(result.length == arr.length)
resolve(result);
);
let p1 = new Promise((resolve, reject)=>
setTimeout(reject, 2000, "P1 rejected");
)
let p2 = new Promise((resolve, reject)=>
setTimeout(resolve, 3000, "P2 resolved");
)
let p3 = new Promise((resolve, reject)=>
setTimeout(resolve, 4000, "P3 resolved");
)
let pResult = myPromiseAll([p1,p2,p3]);
pResult.then(value=>
console.log(pResult); // 是输出成功
console.log(value);
, err =>
console.log(pResult); // 还是输出失败呢?
console.log(err);
)
// 输出
// 很不幸,其中一个失败了
// Promise <state>: "rejected"
// P1 rejected
// P2 resolved
// P3 resolved
为什么最后还是输出了 P2 和 P3 的结果呢? 这是因为,尽管遇到了P1就reject了,然而 P2 和 P3 仍在执行。注意MDN说的是“不管其他Promise是否完成”,而不是“其他Promise被stop”。
let p2 = new Promise((resolve, reject)=>setTimeout(resolve, 3000, "P2 resolved");
)
let p3 = new Promise((resolve, reject)=>
setTimeout(resolve, 4000, "P3 resolved");
)
let pResult = myPromiseAll([p2,55,p3]);
pResult.then(value=>
console.log(pResult);
console.log(value); // 输出 [55, 'P2 resolved', 'P3 resolved']
, err =>
console.log(pResult);
console.log(err);
使用 Promise.all() 在 Promise 实现时执行操作
【中文标题】使用 Promise.all() 在 Promise 实现时执行操作【英文标题】:Perform actions as promises get fulfilled using Promise.all() 【发布时间】:2017-02-05 07:54:12 【问题描述】:我可以用Promise.all(array)
异步解决一堆promise。然而.then()
只会在所有这些承诺都得到解决后运行。当承诺得到解决时,我如何执行操作?
例如,我想使用 Promise.all()
异步加载文章中的所有段落 - 这样网络请求会立即全部触发。如果第 1 段完成加载,我希望它呈现到页面 - 但只有在第 2 段之前完成加载,然后我希望第 2 段加载。如果第 3 段已完成加载而第 2 段未加载,我希望 3 在渲染到页面之前等待 2。以此类推。
我尝试了类似的方法,但我不知道下一步该做什么:
var getStuff = function(number, time)
return new Promise(function(resolve, reject)
window.setTimeout(function()resolve(`$number - Done.`), time);
);
;
Promise.all([ getStuff(1, 200),
getStuff(2, 100),
getStuff(3, 250),
getStuff(4, 200),
getStuff(5, 300),
getStuff(6, 250),
getStuff(7, 5000)])
.then(function(data)
console.log(data);
);
我怎样才能让数据的控制台日志一个接一个地发生 - 在发出下一个请求之前,不用then()
解决每个承诺?有没有更好的方法来做到这一点?
【问题讨论】:
一些 Promise 库有 progress 回调。 没有办法使用原生 es6 获得这种行为?不向我的项目添加另一个库? 为什么不为每个承诺只渲染getStuff(...)
和then
?
我不认为它是本地可用的。不过,您仍然可以拥有一系列承诺,并且每个承诺都有一个单独的 then()
。
@nem035 哦,我错过了那部分。
【参考方案1】:
您无法使用Promise.all
实现此顺序,因为从Promise.all
返回的promise 会等待所提供数组中的所有promise 在它自己解析之前同时(而不是顺序)解析。
相反,您可以单独创建它们的请求的承诺和触发:
// create promises and make concurrent requests
const s1 = getStuff(1, 200);
const s2 = getStuff(2, 100);
const s3 = getStuff(3, 250);
// ...
然后创建一个关于如何处理它们的反应链(stuff1 在 stuff2 之前,stuff2 在 stuff3 之前,等等)
// create a chain of reaction order to the results of parallel promises
s1
.then(console.log) // s1 resolved: log result
.then(() => s2) // chain s2
.then(console.log) // s2 resolved: log result
.then(() => s3) // chain s3
// ...
.then(() => // chain another function at at the end for when all promises resolved
// all promises resolved (all data was logged)
要按照创建 Promise 的顺序对 Promise 结果做出反应,您可以更改 getStuff
函数以使用 Array.prototype.reduce
动态链接反应:
var times = [200, 100, 250, 200, 300, 250, 5000];
var getStuff = function(time, index) // swap the order of arguments so number is the index passed in from Array.map
return new Promise((resolve, reject) =>
window.setTimeout(() =>
resolve(`$index + 1 - Done.`); // use index + 1 because indexes start at 0
, time);
);
;
times
// map each time to a promise (and number to the index of that time + 1) and fire of a request
.map(getStuff)
// dynamically build a reaction chain for the results of promises
.reduce((chain, promise) =>
return chain
.then(() => promise)
.then(console.log);
, Promise.resolve())
.then(() =>
// all promises resolved (all data was logged in order)
);
【讨论】:
啊,这很有道理!基本上,一旦我调用 getStuff,他们就会发出网络请求,如果在之后执行 then 链,我将得到我想要的行为,因为每个 promise 都将在链中调用 .then 之前解决? 一旦你创建了一个 Promise,它就会运行你的请求。在那之后,您可以在任何时候构建链,无论该承诺是否已解决。如果承诺在您创建链之前解决,它将保持其价值,直到您创建链。如果你先做链,链会等待 Promise 接收值。这就是诺言的美妙之处。它们总是异步解析,你总是可以假装在用它们编码时已经有了这些值。【参考方案2】:nem035 的回答很到位。我想指出,在这种情况下,您通常希望在请求发生时采取相同的操作,并在请求全部完成时采取另一个行动。
您可以使用.all
和.map
:
Promise.all([ getStuff(1, 200),
getStuff(2, 100),
getStuff(3, 250),
getStuff(4, 200),
getStuff(5, 300),
getStuff(6, 250),
getStuff(7, 5000)]
.map(request => request.then(v =>
console.log("Request done! Got," v); // or some action per request
return v;
)).then(data => console.log(data));
您可以通过 .map
进一步控制这一点,因为您对每个请求都使用相同的功能:
Promise.all([[1, 200],
[2, 100],
[3, 250],
[4, 200],
[5, 300],
[6, 250],
[7, 5000]])
.map((a, b) => getStuff(a, b))
.map(request => request.then(v =>
console.log("Request done! Got," v); // or some action per request
return v;
)).then(data => console.log(data));
还有:
Promise.all([200, 100, 250, 200, 300, 250, 5000])
.map((a, i) => getStuff(a, i + 1))
.map(request => request.then(v =>
console.log("Request done! Got," v); // or some action per request
return v;
)).then(data => console.log(data));
或与蓝鸟:
const sideEffect = v => console.log("Got partial result", v));
const data = [200, 100, 250, 200, 300, 250, 5000];
Promise.map(data, (a, i) => getStuff(a, i + 1).tap(sideEffect))
.then(data => console.log(data));
当然 - 你应该只修复你的后端,要求客户端对数据的不同部分发出 7 次请求是完全不合理的 - 让后端取值范围。
【讨论】:
【参考方案3】:我知道它不是原生的,但是对于 bluebird,您可以使用 Promise.some
(在实现 count
承诺后完成)或 Promise.mapSeries
(在系列中完成承诺)以某种方式实现您期望的流程。
Bluebird API
【讨论】:
【参考方案4】:正常操作:您可以安全使用Promise.all()
。 Promise 执行器将被并行触发,结果将按照您将 Promise 插入 Promise 数组的顺序返回。然后,您可以按照自己喜欢的方式对它们进行排序。例如在下面的 sn-p 中,我们有五个 Promise,每个 Promise 都会在 5 秒内随机解决。无论他们的解决时间如何,您都会在最新解决时得到结果;
var promises = [ new Promise( v => setTimeout(_ => v("1st paragraph text"),~~(Math.random()*5000))),
new Promise( v => setTimeout(_ => v("2nd paragraph text"),~~(Math.random()*5000))),
new Promise( v => setTimeout(_ => v("3rd paragraph text"),~~(Math.random()*5000))),
new Promise( v => setTimeout(_ => v("4th paragraph text"),~~(Math.random()*5000))),
new Promise( v => setTimeout(_ => v("5th paragraph text"),~~(Math.random()*5000))),
];
Promise.all(promises)
.then(result => console.log(result.reduce((p,c) => p + "\n" + c)));
你想要什么:但是你不想等到最后一个完成,而是想按顺序处理它们,尽快解决它们。那么Array.prototype.reduce()
是你这里最好的朋友。比如
var promises = [ new Promise( v => setTimeout(_ => v("1st paragraph text"),~~(Math.random()*5000))),
new Promise( v => setTimeout(_ => v("2nd paragraph text"),~~(Math.random()*5000))),
new Promise( v => setTimeout(_ => v("3rd paragraph text"),~~(Math.random()*5000))),
new Promise( v => setTimeout(_ => v("4th paragraph text"),~~(Math.random()*5000))),
new Promise( v => setTimeout(_ => v("5th paragraph text"),~~(Math.random()*5000)))
];
promises.reduce((p,c) => p.then(result => (console.log(result + "\n"),c)))
.then(result => (console.log(result + "\n")));
请多次运行代码以查看代码的行为方式。文本将在承诺解决后尽快更新,但前提是轮到它。因此,如果第 1 次在第 2 次之后解决,我们将看到第 1 次和第 2 次按顺序同时出现,但他们不会等待第 3 次解决以此类推...
【讨论】:
以上是关于实现promise.all方法的主要内容,如果未能解决你的问题,请参考以下文章
假设把官方的promise.all去掉,实现自己的promise.all方法
Promise静态方法实现(all race finally resolve reject)