在这个函数堆栈调用结束时,我们得到一个完成几个promise的解析的promise对象。整个promise链是从第一个promise完成而开始的。比如何遍历promise链的机制更重要的是所有这些函数都可以自由使用这个promise传递的值而不影响其他函数。
在这里有两个并发原则。首先,我们通过执行异步操作仅只能处理该值一次; 每个回调函数都可以自由使用此解析值。其次,我们在抽象同步机制方面做得很好。换句话说,代码并没有带有很多重复代码。让我们看看传递promise的代码实际的样子:
//简单实用的工具函数, //将多个较小的函数组合成一个函数。 function compose(...funcs) { return function(value) { var result = value; for(let func of funcs) { result = func(value); } return result; }; } //接受一个promise或一个完成值。 //如果这是一个promise,它添加了一个“then()”回调并返回一个新的promise。 //否则,它会执行“update”并返回值。 function updateFirstName(value) { if (value instanceof Promise) { return value.then(updateFirstName); } console.log('first name', value.first); return value; } //与上面的函数类似, //只是它执行不同的UI“update”。 function updateLastName(value) { if (value instanceof Promise) { return value.then(updateLastName); } console.log('last name', value.last); return value; } //与上面的函数类似,除了它 //只是它执行不同的UI“update”。 function updateAge(value) { if (value instanceof Promise) { return value.then(updateAge); } console.log('age', value.age); return value; } //一个promise对象, //它在延时一秒钟之后, //携带一个数据对象完成promise。 var promise = new Promise((resolve, reject) => { setTimeout(() => { resolve({ first: 'John', last: 'Smith', age: 37 }); }); }, 1000); //我们组装一个“update()”函数来更新各种UI组件。 var update = compose( updateFirstName, updateLastName, updateAge ); //使用promise调用我们的更新函数。 update(promise);这里的关键函数是我们的更新函数 - updateFirstName(),updateLastName()和updateAge()。他们非常灵活,接受一个promise或promise返回值。如果这些函数中的任何一个将promise作为参数,它们会通过添加then()回调函数来返回新的promise。请注意,它添加了相同的函数。updateFirstName()将添加updateFirstName()作为回调。当回调触发时,它将与此次用于更新UI的普通对象一起使用。因此,promise如果失败,我们可以继续更新UI。
promise检查每个函数都需要三行,这并不是非常突兀的。最终结果是易读且灵活的代码。顺序无关紧要; 我们可以用不同的顺序包装我们的update()函数,并且UI组件都将以相同的方式更新。我们可以将普通对象直接传递给update(),一切都会同样执行。看起来不像并发代码的并发代码是我们在这里取得的重大成功。
同步多个promises在本章前面,我们已经探究了单个promise实例,它解析一个值,触发回调,并可能传递给其他promises处理。在本节中,我们将介绍几种静态Promise方法,它们可以帮助我们处理需要同步多个promise值的情况。
首先,我们将处理我们开发的组件需要同步访问多个异步资源的情况。然后,我们将看一下不常见的情况,如异步操作在处理之前由于UI中发生的事件而变得没有意义。
等待promises在我们等待处理多个promise的情况下,也许是将多个数据源转换后提供给一个UI组件使用,我们可以使用Promise.all()方法。它将promise实例的集合作为输入,并返回一个新的promise实例。仅当完成了所有输入的promise时,才会返回一个新实例。
then()函数是我们为Promise提供的创建新promise的回调。给出一组解析值作为输入。这些值对应于索引输入promise的位置。这是一个非常强大的同步机制,它可以帮助我们实现同步并发原则,因为它隐藏了所有的处理记录。
我们不需要几个回调,让每个回调都协调它们所绑定的promise状态,我们只需一个回调,它具有我们需要的所有解析数据。这个示例展示如何同步多个promise:
//用于发送“GET”HTTP请求的工具函数, //并返回带有已解析的数据的promise。 function get(path) { return new Promise((resolve, reject) => { var request = new XMLHttpRequest(); //当数据加载时,完成解析了JSON数据的promise request.addEventListener('load', (e) => { resolve(JSON.parse(e.target.responseText)); }); //当请求出错时, //promise被适当的原因拒绝。 request.addEventListener('error', (e) => { reject(e.target.statusText || 'unknown error'); }); //如果请求被中止,我们继续完成处理请求 request.addEventListener('abort', resolve); request.open('get', path); request.send(); }); } //保存我们的请求promises。 var requests = []; //发出5个API请求,并将相应的5个 //promise放在“requests”数组中。 for (let i = 0; i < 5; i++) { requests.push(get('api.json')); } //使用“Promise.all()”让我们传入一个数组promises, //当所有promise完成时,返回一个已经完成的新promise。 //我们的回调得到一个数组对应于promises的已解析值。 Promise.all(requests).then((values) => { console.log('first', values.map(x => x[0])); console.log('second', values.map(x => x[1])); }); 取消promises