介绍
在电子商务网站中,用户可以通过鼠标或手势交互实现 360 度全方位查看产品,提升用户体验。现在需要你设计一个 Pipeline
管道函数,用于控制 360 度展示产品的动画序列,通过管道连接各个动画步骤,使产品以流畅的方式呈现给用户。
准备
开始答题前,需要先打开本题的项目代码文件夹,目录结构如下:
├── css
├── effect.gif
├── js
│ ├── index.js
│ └── utils.js
└── index.html
其中:
css
是样式文件夹。index.html
是主页面。js/index.js
是动画序列代码的 js 文件。js/utils.js
是待补充代码的 js 文件。effect.gif
是页面最终的效果图。
在浏览器中预览 index.html
页面效果如下:
目标
请在 js/utils.js
文件中的 TODO 部分,实现以下目标:
封装一个支持异步的 pipeline
管道函数,能够按顺序执行一系列异步操作,每个异步操作的结果将作为下一个异步操作的输入。
函数参数说明:
initialValue
:管道的初始值(即sequence
中第一个函数的参数)。它是整个异步管道的起点。第一个异步步骤将以此值开始,并且后续步骤将在前一步骤的输出基础上进行。sequence
:是一个由具有返回值和可以传参的函数组成的数组,函数可以是普通函数也可以是 Promise 函数。每个函数接收前一个步骤的输出(即该函数的参数是上一个函数的执行结果),并返回一个 Promise。这个数组定义了整个管道中的处理步骤和它们的顺序。
函数返回值说明:
pipeline
函数返回一个Promise
,这个Promise
最终解析为整个管道执行完成后的结果。
测试用例如下:
请注意以下用例仅供参考,实际判题时会修改测试用例,请保证代码的通用性。
- 示例 1:
sequence
中都为普通函数
function fn1(data){ return data +'2.开始左转'}function fn2(data){ return data+'3.开始右转'}pipeline('1.动画开始',[fn1, fn2]).then(res => {console.log(res) })
// 打印结果为:'1.动画开始2.开始左转3.开始右转'
- 示例 2:
sequence
中都为promise
函数
function fn1(data){ return new Promise(resolve => {resolve(data+'->执行第一个动画')})
}
function fn2(data){ return new Promise(resolve => {resolve(data+'->执行第二个动画')})
}
pipeline('动画开始',[fn1, fn2]).then(res => {console.log(res)
})
// 打印结果为:'动画开始->执行第一个动画->执行第二个动画'
完成后,点击屏幕触发动画序列,最终效果可参考文件夹下面的 gif 图,图片名称为 effect.gif
(提示:可以通过 VS Code 或者浏览器预览 gif 图片)。
规定
- 请勿修改
js/utils.js
文件外的任何内容。 - 判题时会随机提供不同参数对
pipeline
函数功能进行检测,请保证函数的通用性,不能仅对测试数据有效。 - 请严格按照考试步骤操作,切勿修改考试默认提供项目中的文件名称、文件夹路径、class 名、id 名、图片名等,以免造成判题⽆法通过。
判分标准
sequence
参数都为普通函数且满足需求正确输出,得 5 分。sequence
参数都为promise
函数且满足需求正确输出,得 5 分。
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>产品360度展示</title><link rel="stylesheet" href="./css/style.css">
</head><body><h1>产品展示</h1><p class="tip">点击屏幕,360度产品展示</p><div class="computer"><div class="inner"><div class="screen"><div class="face-one"><div class="camera"></div><div class="display"><div class="shade"><div class="computer-screen"><div class="loading-bar"></div><div class="loading-text">正在启动...</div></div></div></div><span>蓝桥云课</span></div><title>Layer 1</title></div><div class="computerbody"><div class="face-one"><div class="touchpad"></div><div class="keyboard"><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key space"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key"></div><div class="key f"></div><div class="key f"></div><div class="key f"></div><div class="key f"></div><div class="key f"></div><div class="key f"></div><div class="key f"></div><div class="key f"></div><div class="key f"></div><div class="key f"></div><div class="key f"></div><div class="key f"></div><div class="key f"></div><div class="key f"></div><div class="key f"></div><div class="key f"></div></div></div><div class="pad one"></div><div class="pad two"></div><div class="pad three"></div><div class="pad four"></div></div></div><div class="shadow"></div></div><script src="./js/utils.js"></script><script src="./js/index.js"></script>
</body></html>
方法一
/*** @param {*} initialValue 初始值* @param {Array} sequence 由普通函数或 Promise 函数组成的数组* @return {Promise} */const pipeline = (initialValue, sequence) => {// TODO: 待补充代码// 创建 Promist 对象 修改状态为已完成状态let promise = new Promise((resolve,reject)=>{resolve(initialValue);console.log('initialValue,已成功');})// console.log(promise);// sequence.forEach((item)=>{// // 成功回调// promise = promise.then(// value=>{// console.log(value);// return item(value)// }// )// }) for(let p of sequence){promise = promise.then(value=>{return p(value)})}return promise;
};// 检测需要,请勿删除
try {module.exports = { pipeline };
} catch { }
let promise = new Promise((resolve, reject) => {resolve(initialValue);console.log('initialValue,已成功');
});
- 这里创建了一个 已完成状态的 Promise,初始值为
initialValue
。 resolve(initialValue)
会立即将这个 Promise 的状态设置为fulfilled
,并将initialValue
作为解决值。
for...of
循环遍历函数数组
for (let p of sequence) {promise = promise.then(value => {return p(value)})
}
for...of
循环:这是 ES6 引入的遍历迭代器的语法,用于遍历sequence
数组中的每个函数p
。- 每次迭代中,
promise
会被更新为一个新的 Promise,这个新 Promise 是通过调用当前promise.then(...)
创建的。 - 在
then
的回调函数中,当前函数p
会接收前一个 Promise 的结果value
,并返回处理后的新值(或新的 Promise)。 - 这样,每个函数
p
会在前一个函数执行完毕后执行,形成一个 链式调用,确保函数按顺序执行。
return promise;
- 循环结束后,
promise
已经是一个包含所有函数处理逻辑的 Promise 链。当这个 Promise 被 resolve 时,会按顺序执行sequence
中的所有函数,并将最终结果传递下去。
for...of
循环的作用
for...of
循环在这里的关键作用是 将函数数组 sequence
转换为一个连续的 Promise 链:
- 顺序执行:
sequence
中的函数会按照数组顺序依次执行,每个函数依赖前一个函数的结果。 - 动态构建 Promise 链:每次迭代通过
then
方法将当前函数添加到 Promise 链中,确保异步操作的顺序性。 - 简洁性:相比传统的
forEach
循环,for...of
语法更简洁,且更符合遍历迭代器的场景。
完整执行流程示例
假设 sequence
是 [func1, func2, func3]
,则执行流程如下:
- 初始
promise
是一个已 resolve 的 Promise,值为initialValue
。 - 第一次循环(处理
func1
):promise
变为promise.then(value => func1(value))
。 - 第二次循环(处理
func2
):promise
变为promise.then(value => func2(value))
,此时value
是func1
的返回值。 - 第三次循环(处理
func3
):promise
变为promise.then(value => func3(value))
,此时value
是func2
的返回值。 - 最终返回的
promise
会在所有函数执行完毕后 resolve,结果为func3
的返回值。
方法二
const pipeline = async (initialValue, sequence) => {// TODO: 待补充代码let argument = initialValue;for (let p of sequence) {argument = await p(argument);}return argument;
};
pipeline
是一个 异步函数,用于按顺序执行一系列异步操作(或同步操作),并将前一个操作的结果传递给下一个操作。它接收一个初始值 initialValue
和一个函数数组 sequence
,最终返回所有操作处理后的结果。
async
关键字表示这是一个异步函数,允许在函数内部使用await
处理异步操作。initialValue
:初始输入值,会被传递给sequence
中的第一个函数。sequence
:一个由多个函数组成的数组,每个函数将按顺序处理前一个函数的结果。argument
变量用于保存每一步处理的中间结果。初始时,它的值为initialValue
。for...of
循环:遍历sequence
数组中的每个函数p
。await p(argument)
:- 调用当前函数
p
,并将argument
作为参数传递给它。 await
会暂停当前函数的执行,等待p(argument)
的结果(无论p
是同步函数还是返回 Promise 的异步函数)。- 将
p(argument)
的结果重新赋值给argument
,以便作为下一个函数的输入。
- 调用当前函数
- 循环结束后,
argument
保存了所有函数处理后的最终结果,通过return
返回。 -
核心逻辑说明
- 顺序执行:
sequence
中的函数会按照数组顺序依次执行,每个函数依赖前一个函数的结果。 - 异步支持:
- 如果
sequence
中的函数是异步的(返回 Promise),await
会确保等待其完成后再执行下一个函数。 - 如果函数是同步的,
await
会直接返回其结果,不影响执行顺序。
- 如果
- 数据传递:每个函数的输出作为下一个函数的输入,形成一条 处理管道。