Promise炖锅之应用

概述

本章列举具体应用场景以及常见大厂面试题

前文

Promise炖锅之API篇 Promise炖锅之微任务

应用场景

多个请求并行并处理一致

发起多个请求,当这些请求都返回数据了,再执行loading操作;若其中有一个请求失败了,立即执行loading操作

  • 分析

需要多个请求,请求间无顺序要求;都返回数据后才执行接下来操作,这里是同步操作;需要用到Promise.all

  • 代码
// 1.请求图片
const getPics = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("pics")
    }, 300)
  })
};
// 2.请求标题
const getTitle = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("titles")
    }, 800)
  })
};

// loading show
console.log("loading show")
Promise.all([getPics(),getTitle()]).then(res => {
  console.log(res);
  // loading hide
  console.log("loading hide")
}).catch(e => {
  console.log(e);
  // loading hide
  console.log("loading hide")
})

// 输出
loading show
["pics", "titles"]
loading hide
// 此处用打印loading来模拟loading效果
复制代码

多个请求并行但处理不一致

一个页面,需要发起多个请求,无论成功与否,都要能在下一步捕获到

  • 分析

与前一个场景相比,要求请求无论成功或失败都能捕获到。若在Promise.all后直接catch,那么第一个任务失败后后面的任务不会执行;所以有两种方案:1.Promise.all后考虑单独捕获任务,然后转为成功状态,交由then处理(此时能获取所有任务状态)2.Promise.allSettled方案

  • 代码1(不是很推荐)
// 1.请求图片
const getPics = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject("pics")
    }, 300)
  })
};
// 2.请求标题
const getTitle = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("titles")
    }, 800)
  })
};

Promise.all([getPics().catch(err => err),getTitle().catch(err => err)]).then(res => {
  if(res[0] === "pics"){
    console.log("getPics reject")
  }
  if(res[1] === "titles"){
    console.log("getTitle resolve")
  }
})
// 输出
getPics reject
getTitle resolve
复制代码
  • point

上述为了简单,直接在catch里抛出了err,其实可以做很多处理的

  • 代码2
// 1.请求图片
const getPics = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject("pics")
    }, 300)
  })
};
// 2.请求标题
const getTitle = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("titles")
    }, 800)
  })
};

Promise.allSettled([getPics(),getTitle()]).then(res => {
  if(res[0].status === "rejected"){
    console.log("getPics reject")
  }
  if(res[1].status === "fulfilled"){
    console.log("getTitle resolve")
  }
})
// 输出
getPics reject
getTitle resolve
复制代码

请求超时处理

某个请求,若一定时间内未返回,则返回超时

  • 分析

此处有2个处理,一个是图片请求,一个是定时处理,谁的状态先确定下来就以谁的为准,采用Promise.race

  • 代码
// 1.请求图片
const getPics = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject("pics")
    }, 3000)
  })
};
// 2.超时
const timeout = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject("超时")
    }, 1000)
  })
};

Promise.race([getPics(),timeout()]).then(res => {
  console.log(res)
}).catch(err => {
  // 超时的reject或者图片请求的reject
  console.log(err);
})
// 输出
超时
复制代码

面试题

使用Promise实现每隔1秒输出1,2,3

  • 分析

每隔一秒输出,可以想到用setTimeout;按顺序输出,可以想到链式then调用

  • 第一版
new Promise(resolve => {
  resolve()
}).then(() => {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log(1);
      resolve();
    }, 1000)
  })
}).then(() => {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log(2);
      resolve();
    }, 1000)
  })
}).then(() => {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log(3);
      resolve();
    }, 1000)
  })  
})
// 该版比较基本,缺点明显,重复代码过多,且把输出数组细化到了代码里
复制代码
  • 分析

考虑抽出重复代码,将数组定义成变量;前者就是重复执行,类似map;但要考虑then是链式操作,下一次执行依赖于上一次结果,因而考虑reduce 代码一开始有一个resolve(),这部分是不重复的,可以考虑用作reduce的初始值;然后注意接下来对数组的操作,都是返回了一个带then回调的promise,最终的结果是一个链式调用then 所以考虑在reduce执行方法中,对于入参(旧的promise)的基础上,return一个新的带then的promise

  • 第二版
const arr = [1,2,3];
arr.reduce( (prePromise, item) => {
  return prePromise.then(() => {
    return new Promise(resolve => {
      setTimeout(() => {
        console.log(item);
        resolve();
      }, 1000)
    })
  })
}, Promise.resolve())
复制代码

使用Promise实现红绿灯交替重复亮

红灯3秒亮一次,黄灯2秒亮一次,绿灯1秒亮一次,让三个灯不断交替重复亮灯,三个亮灯函数已经存在

  • 分析

个人觉得题目不是很清晰,其实要实现的是,红灯等待3秒后执行一次,【然后】绿灯等待2秒执行一次,【然后】黄灯1秒执行一次,然后按顺序一直执行 注意然后,这里可以构成一个then的关系;等待多少秒,可以用定时器;按顺序一直执行,可以用递归

  • 代码
function red() {
    console.log('red');
}
function green() {
    console.log('green');
}
function yellow() {
    console.log('yellow');
}

var light = function (timmer, cb) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            cb();
            resolve();
        }, timmer);
    });
};

var step = function () {
    Promise.resolve().then(function () {
        return light(3000, red);
    }).then(function () {
        return light(2000, green);
    }).then(function () {
        return light(1000, yellow);
    }).then(function () {
        step();
    });
}

step();
复制代码

实现 mergePromise 函数

实现mergePromise函数,把传进去的数组按顺序先后执行,并且把返回的数据先后放到数组data中

const time = (timer) => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve()
    }, timer)
  })
}
const ajax1 = () => time(2000).then(() => {
  console.log(1);
  return 1
})
const ajax2 = () => time(1000).then(() => {
  console.log(2);
  return 2
})
const ajax3 = () => time(1000).then(() => {
  console.log(3);
  return 3
})

function mergePromise () {
  // 在这里写代码
}

mergePromise([ajax1, ajax2, ajax3]).then(data => {
  console.log("done");
  console.log(data); // data 为 [1, 2, 3]
});

// 要求分别输出
// 1
// 2
// 3
// done
// [1, 2, 3]
复制代码
  • 分析

乍一看,有点像Promise.all;但又要依次输出,我们再看上面的时延,明显前者是不行,所以考虑Promise如何才能同步执行,链式then 最后的data会依次得到上述的结果,考虑将上述结果依次push进某个数组,执行完毕后再打印

  • 代码
function mergePromise (ajaxArray) {
  // 存放每个ajax的结果
  let data = [];
  let promise = Promise.resolve();
  ajaxArray.forEach(ajax => {
  	// 第一次的then为了用来调用ajax
  	// 第二次的then是为了获取ajax的结果
        // 这里对promise重新赋值,其实是相当于延长了 Promise 链
    promise = promise.then(ajax).then(res => {
      data.push(res);
      return data; // 把每次的结果返回
    })
  })
  // 最后得到的promise它的值就是data
  return promise;
}
复制代码

限制异步操作的并发个数并尽可能快的完成全部

常见的是图片加载,有最大并发加载限制:有N个图片url,最大支持3个并发(此题建议直接看参考链接一)

  var urls = [
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting1.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting2.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting3.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting4.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting5.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn6.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn7.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn8.png",
];
function loadImg(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = function() {
      console.log("一张图片加载完成");
      resolve(img);
    };
    img.onerror = function() {
    	reject(new Error('Could not load image at' + url));
    };
    img.src = url;
  });

复制代码
  • 分析

无,原因看上

  • 代码
function limitLoad(urls, handler, limit) {
  let sequence = [].concat(urls); // 复制urls
  // 这一步是为了初始化 promises 这个"容器"
  let promises = sequence.splice(0, limit).map((url, index) => {
    return handler(url).then(() => {
      // 返回下标是为了知道数组中是哪一项最先完成
      return index;
    });
  });
  // 注意这里要将整个变量过程返回,这样得到的就是一个Promise,可以在外面链式调用
  return sequence
    .reduce((pCollect, url) => {
      return pCollect
        .then(() => {
          return Promise.race(promises); // 返回已经完成的下标
        })
        .then(fastestIndex => { // 获取到已经完成的下标
        	// 将"容器"内已经完成的那一项替换
          promises[fastestIndex] = handler(url).then(
            () => {
              return fastestIndex; // 要继续将这个下标返回,以便下一次变量
            }
          );
        })
        .catch(err => {
          console.error(err);
        });
    }, Promise.resolve()) // 初始化传入
    .then(() => { // 最后三个用.all来调用
      return Promise.all(promises);
    });
}
limitLoad(urls, loadImg, 3)
  .then(res => {
    console.log("图片全部加载完毕");
    console.log(res);
  })
  .catch(err => {
    console.error(err);
  });
复制代码

参考

要就来45道Promise面试题一次爽到底

前端 Promise 常见的应用场景

猜你喜欢

转载自juejin.im/post/6976222394168180772