“金三银四,金九银十”,都是要收获的季节。面对各种面试题,各种概念、原理都要去记,挺枯燥的。本文是面向面试题和实际使用谈一下Promise。

Promise是什么?

  Promise是JS异步编程中的重要概念,异步抽象处理对象,是目前比较流行Javascript异步编程解决方案之一。这句话说的很明白了,Promise是一种用于解决异步问题的思路、方案或者对象方式。在js中,经常使用异步的地方是Ajax交互。比如在es5时代,jQueryajax的使用success来完成异步的:

  1. $.ajax({
  2. url:'/xxx',
  3. success:()=>{},
  4. error: ()=>{}
  5. })

  这种方法可以清楚的让读代码的人明白那一部分是Ajax请求成功的回调函数和失败的回调函数。但是问题来了,当一次请求需要连续请求多个接口时,这段代码仿佛进入了一团乱麻中:

  1. // 第一次
  2. $.ajax({
  3. url:'/xxx',
  4. success:()=>{
  5. // 第二次
  6. $.ajax({
  7. url:'/xxx',
  8. success:()=>{
  9. // 第三次
  10. $.ajax({
  11. url:'/xxx',
  12. success:()=>{
  13. // 可能还会有
  14. },
  15. error: ()=>{}
  16. })
  17. },
  18. error: ()=>{}
  19. })
  20. },
  21. error: ()=>{}
  22. })

  也许因为success和error这两个函数的存在,理解这段代码会很简单,但是当我们更改需求的时候,这将成为一个棘手的问题。这就是回调地狱。

  当然,这是es5时代。当js这门语言发展到es6时代时,Promise的出现给异步带来了变革。Promise提供一个then,来为异步提供回调函数:

  1. $.ajax({
  2. url:'/xxx',
  3. }).then( ()=>{
  4. // 成功的回调
  5. }, ()=>{
  6. // 失败的回调
  7. })

  而其先进之处则是,可以在then方法中继续写Promise对象并返回,然后继续调用then来进行回调操作。

Promise的用法

  说完了Promise是什么,下面让我们研究一下Promise怎么使用。首先,Promise是一个对象,因此,我们使用new的方式新建一个。然后给它传一个函数作为参数,这个函数呢也有两个参数,一个叫resolve(决定),一个叫reject(拒绝),这两个参数也是函数。紧接着,我们使用then来调用这个Promise:

  1. const fn = new Promise(function (resolve, reject) {
  2. setTimeout(()=>{
  3. let num = Math.ceil(Math.random() * 10) // 假设num为7
  4. if (num > 5) {
  5. resolve(num) //返回7
  6. } else {
  7. reject(num)
  8. }
  9. },2000)
  10. })
  11. fn.then((res)=>{
  12. console.log(res) //
  13. },(err)=>{
  14. console.log(err)
  15. })

  这就是最简单的Promise的使用。假设2秒钟之后生成随机数为7,因此resolve回调函数运行,then走第一个函数,console.log(7)。假设2秒钟之后生成随机数为3,因此reject回调函数运行,then走第二个函数,console.log(3)。

  那你可能说了,Promise要是就这点能耐也没什么大不了的啊?我们上面说了Promise的先进之处在于可以在then方法中继续写Promise对象并返回,然后继续调用then来进行回调操作:

  1. fn = new Promise(function (resolve, reject) {
  2. let num = Math.ceil(Math.random() * 10)
  3. if (num > 5) {
  4. resolve(num)
  5. } else {
  6. reject(num)
  7. }
  8. })
  9. // 第一次回调
  10. fn.then((res)=>{
  11. console.log(`res==>${res}`)
  12. return new Promise((resolve,reject)=>{
  13. if(2*res>15){
  14. resolve(2*res)
  15. }else{
  16. reject(2*res)
  17. }
  18. })
  19. },(err)=>{
  20. console.log(`err==>${err}`)
  21. }).then((res)=>{ // 第二次回调
  22. console.log(res)
  23. },(err)=>{
  24. console.log(`err==>${err}`)
  25. })

  这就可以代替了上面类似es5时代的jQurey的success的嵌套式的回调地狱的产生,让代码清爽了许多。这里的resolve就相当于以前的success。

Promise的原理

  在Promise的内部,有一个状态管理器的存在,有三种状态:pending、fulfilled、rejected。

    (1) promise 对象初始化状态为 pending。

    (2) 当调用resolve(成功),会由pending => fulfilled。

    (3) 当调用reject(失败),会由pending => rejected。

  因此,看上面的的代码中的resolve(num)其实是将promise的状态由pending改为fulfilled,然后向then的成功回掉函数传值,reject反之。但是需要记住的是注意promsie状态 只能由 pending => fulfilled/rejected, 一旦修改就不能再变(记住,一定要记住,下面会考到)。

  当状态为fulfilled(rejected反之)时,then的成功回调函数会被调用,并接受上面传来的num,进而进行操作。promise.then方法每次调用,都返回一个新的promise对象 所以可以链式写法(无论resolve还是reject都是这样)。

Promise的几种方法

then

  then方法用于注册当状态变为fulfilled或者reject时的回调函数:

  1. // onFulfilled 是用来接收promise成功的值
  2. // onRejected 是用来接收promise失败的原因
  3. promise.then(onFulfilled, onRejected);

  需要注意的地方是then方法是异步执行的。

  1. // resolve(成功) onFulfilled会被调用
  2. const promise = new Promise((resolve, reject) => {
  3. resolve('fulfilled'); // 状态由 pending => fulfilled
  4. });
  5. promise.then(result => { // onFulfilled
  6. console.log(result); // 'fulfilled'
  7. }, reason => { // onRejected 不会被调用
  8. })
  9.  
  10. // reject(失败) onRejected会被调用
  11. const promise = new Promise((resolve, reject) => {
  12. reject('rejected'); // 状态由 pending => rejected
  13. });
  14. promise.then(result => { // onFulfilled 不会被调用
  15. }, reason => { // onRejected
  16. console.log(rejected); // 'rejected'
  17. })

catch

  catch在链式写法中可以捕获前面then中发送的异常。

  1. fn = new Promise(function (resolve, reject) {
  2. let num = Math.ceil(Math.random() * 10)
  3. if (num > 5) {
  4. resolve(num)
  5. } else {
  6. reject(num)
  7. }
  8. })
  9. fn..then((res)=>{
  10. console.log(res)
  11. }).catch((err)=>{
  12. console.log(`err==>${err}`)
  13. })

  其实,catch相当于then(null,onRejected),前者只是后者的语法糖而已。

resolve、reject

  Promise.resolve 返回一个fulfilled状态的promise对象,Promise.reject 返回一个rejected状态的promise对象。

  1. Promise.resolve('hello').then(function(value){
  2. console.log(value);
  3. });
  4.  
  5. Promise.resolve('hello');
  6. // 相当于
  7. const promise = new Promise(resolve => {
  8. resolve('hello');
  9. });
  10.  
  11. // reject反之

all

  但从字面意思上理解,可能为一个状态全部怎么样的意思,让我看一下其用法,就可以看明白这个静态方法:

  1. var p1 = Promise.resolve(1),
  2. p2 = Promise.reject(2),
  3. p3 = Promise.resolve(3);
  4. Promise.all([p1, p2, p3]).then((res)=>{
  5. //then方法不会被执行
  6. console.log(results);
  7. }).catch((err)=>{
  8. //catch方法将会被执行,输出结果为:2
  9. console.log(err);
  10. });

  大概就是作为参数的几个promise对象一旦有一个的状态为rejected,则all的返回值就是rejected。

  当这几个作为参数的函数的返回状态为fulfilled时,至于输出的时间就要看谁跑的慢了:

  1. let p1 = new Promise((resolve)=>{
  2. setTimeout(()=>{
  3. console.log('1s') //1s后输出
  4. resolve(1)
  5. },1000)
  6. })
  7. let p10 = new Promise((resolve)=>{
  8. setTimeout(()=>{
  9. console.log('10s') //10s后输出
  10. resolve(10)
  11. },10000)
  12. })
  13. let p5 = new Promise((resolve)=>{
  14. setTimeout(()=>{
  15. console.log('5s') //5s后输出
  16. resolve(5)
  17. },5000)
  18. })
  19. Promise.all([p1, p10, p5]).then((res)=>{
  20. console.log(res); // 最后输出
  21. })

  这段代码运行时,根据看谁跑的慢的原则,则会在10s之后输出[1,10,5]。over,all收工。

race

  promise.race()方法也可以处理一个promise实例数组但它和promise.all()不同,从字面意思上理解就是竞速,那么理解起来上就简单多了,也就是说在数组中的元素实例那个率先改变状态,就向下传递谁的状态和异步结果。但是,其余的还是会继续进行的。

  1. let p1 = new Promise((resolve)=>{
  2. setTimeout(()=>{
  3. console.log('1s') //1s后输出
  4. resolve(1)
  5. },1000)
  6. })
  7. let p10 = new Promise((resolve)=>{
  8. setTimeout(()=>{
  9. console.log('10s') //10s后输出
  10. resolve(10) //不传递
  11. },10000)
  12. })
  13. let p5 = new Promise((resolve)=>{
  14. setTimeout(()=>{
  15. console.log('5s') //5s后输出
  16. resolve(5) //不传递
  17. },5000)
  18. })
  19. Promise.race([p1, p10, p5]).then((res)=>{
  20. console.log(res); // 最后输出
  21. })

  因此,在这段代码的结尾我们的结果为

  1. 1s
  2. 1
  3. 5s
  4. 10s

  我们可以根据race这个属性做超时的操作:

  1. //请求某个图片资源
  2. let requestImg = new Promise(function(resolve, reject){
  3. var img = new Image();
  4. img.onload = function(){
  5. resolve(img);
  6. }
  7. });
  8. //延时函数,用于给请求计时
  9. let timeOut = new Promise(function(resolve, reject){
  10. setTimeout(function(){
  11. reject('图片请求超时');
  12. }, 5000);
  13. });
  14.  
  15. Promise.race([requestImg, timeout]).then((res)=>{
  16. console.log(res);
  17. }).catch((err)=>{
  18. console.log(err);
  19. });

Promise相关的面试题

1.

  1. const promise = new Promise((resolve, reject) => {
  2. console.log(1);
  3. resolve();
  4. console.log(2);
  5. });
  6. promise.then(() => {
  7. console.log(3);
  8. });
  9. console.log(4);

  输出结果为:1,2,4,3。

  解题思路:then方法是异步执行的。

2.

  1. const promise = new Promise((resolve, reject) => {
  2. setTimeout(() => {
  3. resolve('success')
  4. reject('error')
  5. }, 1000)
  6. })
  7. promise.then((res)=>{
  8. console.log(res)
  9. },(err)=>{
  10. console.log(err)
  11. })

  输出结果:success

  解题思路:Promise状态一旦改变,无法在发生变更。

3.

  1. Promise.resolve(1)
  2. .then(2)
  3. .then(Promise.resolve(3))
  4. .then(console.log)

  输出结果:1

  解题思路:Promise的then方法的参数期望是函数,传入非函数则会发生值穿透。

4.

  1. setTimeout(()=>{
  2. console.log('setTimeout')
  3. })
  4. let p1 = new Promise((resolve)=>{
  5. console.log('Promise1')
  6. resolve('Promise2')
  7. })
  8. p1.then((res)=>{
  9. console.log(res)
  10. })
  11. console.log(1)

  输出结果:

    Promise1
    1
    Promise2
    setTimeout

  解题思路:这个牵扯到js的执行队列问题,整个script代码,放在了macrotask queue中,执行到setTimeout时会新建一个macrotask queue。但是,promise.then放到了另一个任务队列microtask queue中。script的执行引擎会取1个macrotask queue中的task,执行之。然后把所有microtask queue顺序执行完,再取setTimeout所在的macrotask queue按顺序开始执行。(具体参考https://www.zhihu.com/question/36972010

 5.
  1. Promise.resolve(1)
  2. .then((res) => {
  3. console.log(res);
  4. return 2;
  5. })
  6. .catch((err) => {
  7. return 3;
  8. })
  9. .then((res) => {
  10. console.log(res);
  11. });

  输出结果:1  2

  解题思路:Promise首先resolve(1),接着就会执行then函数,因此会输出1,然后在函数中返回2。因为是resolve函数,因此后面的catch函数不会执行,而是直接执行第二个then函数,因此会输出2。

6.

  1. const promise = new Promise((resolve, reject) => {
  2. setTimeout(() => {
  3. console.log('开始');
  4. resolve('success');
  5. }, 5000);
  6. });
  7.  
  8. const start = Date.now();
  9. promise.then((res) => {
  10. console.log(res, Date.now() - start);
  11. });
  12.  
  13. promise.then((res) => {
  14. console.log(res, Date.now() - start);
  15. });

  输出结果:

    开始

    success 5002

    success 5002

  解题思路:promise 的.then或者.catch可以被调用多次,但这里 Promise 构造函数只执行一次。或者说 promise 内部状态一经改变,并且有了一个值,那么后续每次调用.then 或者.catch都会直接拿到该值。

7.

  1. let p1 = new Promise((resolve,reject)=>{
  2. let num = 6
  3. if(num<5){
  4. console.log('resolve1')
  5. resolve(num)
  6. }else{
  7. console.log('reject1')
  8. reject(num)
  9. }
  10. })
  11. p1.then((res)=>{
  12. console.log('resolve2')
  13. console.log(res)
  14. },(rej)=>{
  15. console.log('reject2')
  16. let p2 = new Promise((resolve,reject)=>{
  17. if(rej*2>10){
  18. console.log('resolve3')
  19. resolve(rej*2)
  20. }else{
  21. console.log('reject3')
  22. reject(rej*2)
  23. }
  24. })
      return p2
  25. }).then((res)=>{
  26. console.log('resolve4')
  27. console.log(res)
  28. },(rej)=>{
  29. console.log('reject4')
  30. console.log(rej)
  31. })

  输出结果:

    reject1
    reject2
    resolve3
    resolve4
    12

  解题思路:我们上面说了Promise的先进之处在于可以在then方法中继续写Promise对象并返回。

8.重头戏!!!!实现一个简单的Promise
  1. function Promise(fn){
  2. var status = 'pending'
  3. function successNotify(){
  4. status = 'fulfilled'//状态变为fulfilled
  5. toDoThen.apply(undefined, arguments)//执行回调
  6. }
  7. function failNotify(){
  8. status = 'rejected'//状态变为rejected
  9. toDoThen.apply(undefined, arguments)//执行回调
  10. }
  11. function toDoThen(){
  12. setTimeout(()=>{ // 保证回调是异步执行的
  13. if(status === 'fulfilled'){
  14. for(let i =0; i< successArray.length;i ++) {
  15. successArray[i].apply(undefined, arguments)//执行then里面的回掉函数
  16. }
  17. }else if(status === 'rejected'){
  18. for(let i =0; i< failArray.length;i ++) {
  19. failArray[i].apply(undefined, arguments)//执行then里面的回掉函数
  20. }
  21. }
  22. })
  23. }
  24. var successArray = []
  25. var failArray = []
  26. fn.call(undefined, successNotify, failNotify)
  27. return {
  28. then: function(successFn, failFn){
  29. successArray.push(successFn)
  30. failArray.push(failFn)
  31. return undefined // 此处应该返回一个Promise
  32. }
  33. }
  34. }

  解题思路:Promise中的resolve和reject用于改变Promise的状态和传参,then中的参数必须是作为回调执行的函数。因此,当Promise改变状态之后会调用回调函数,根据状态的不同选择需要执行的回调函数。

总结

  首先,Promise是一个对象,如同其字面意思一样,代表了未来某时间才会知道结果的时间,不受外界因素的印象。Promise一旦触发,其状态只能变为fulfilled或者rejected,并且已经改变不可逆转。Promise的构造函数接受一个函数作为参数,该参数函数的两个参数分别为resolve和reject,其作用分别是将Promise的状态由pending转化为fulfilled或者rejected,并且将成功或者失败的返回值传递出去。then有两个函数作为Promise状态改变时的回调函数,当Promise状态改变时接受传递来的参数并调用相应的函数。then中的回调的过程为异步操作。catch方法是对.then(null,rejectFn)的封装(语法糖),用于指定发生错误时的回掉函数。一般来说,建议不要再then中定义rejected状态的回调函数,应该使用catch方法代替。all和race都是竞速函数,all结束的时间取决于最慢的那个,其作为参数的Promise函数一旦有一个状态为rejected,则总的Promise的状态就为rejected;而race结束的时间取决于最快的那个,一旦最快的那个Promise状态发生改变,那个其总的Promise的状态就变成相应的状态,其余的参数Promise还是会继续进行的。

  当然在es7时代,也出现了await/async的异步方案,这会是我们以后谈论的。

面向面试题和实际使用谈promise的更多相关文章

  1. 也谈Promise

    最新的ES6标准添加有Promise方法,但自己在项目中一直使用jQuery(jQuery自己实现了不标准的Promise),加上es6标准还没有得到普及,也就懒得学习相关资料. 最近手头上的活少了, ...

  2. 浅谈Promise

    学习过JavaScript的人都知道,JavaScript是单线程作业,这样会有一个很大的缺陷,所有的Ajax,浏览器事件等,都是通过异步去完成.所谓的同步和异步最大的区别无非就是在于同步会阻塞后续代 ...

  3. 再谈Promise

    方法 构造函数 接受的参数是一个带两个Function参数的函数,实际的异步代码编写在这个函数里,成功后调用第一个参数,失败调用第二个: Promise.prototype.catch 当构造函数里调 ...

  4. 再谈 Promise

    读完这篇文章,预计会消耗你 40 分钟的时间. Ajax 出现的时候,刮来了一阵异步之风,现在 Nodejs 火爆,又一阵异步狂风刮了过来.需求是越来越苛刻,用户对性能的要求也是越来越高,随之而来的是 ...

  5. 浅谈Promise原理与应用

    在JavaScript中,所有代码都是单线程.由于该“缺陷”,JavaScript在处理网络操作.事件操作时都是需要进行异步执行的.AJAX就是一个典型的异步操作 对于异步操作,有传统的利用回调函数和 ...

  6. async/await,了解一下?

    上一篇博客我们在现实使用和面试角度讲解了Promise(原文可参考<面向面试题和实际使用谈promise>),但是Promise 的方式虽然解决了 callback hell,但是这种方式 ...

  7. python 全栈开发,Day94(Promise,箭头函数,Django REST framework,生成json数据三种方式,serializers,Postman使用,外部python脚本调用django)

    昨日内容回顾 1. 内容回顾 1. VueX VueX分三部分 1. state 2. mutations 3. actions 存放数据 修改数据的唯一方式 异步操作 修改state中数据的步骤: ...

  8. 一篇文章彻底搞懂es6 Promise

    前言 Promise,用于解决回调地狱带来的问题,将异步操作以同步的操作编程表达出来,避免了层层嵌套的回调函数. 既然是用来解决回调地狱的问题,那首先来看下什么是回调地狱 var sayhello = ...

  9. C语言程序试题

    一个无向连通图G点上的哈密尔顿(Hamiltion)回路是指从图G上的某个顶点出发,经过图上所有其他顶点一次且仅一次,最后回到该顶点的路劲.一种求解无向图上哈密尔顿回路算法的基础实现如下: 假设图G存 ...

随机推荐

  1. poj2793 素数和

    题目链接:http://poj.org/problem?id=2739 #include<iostream> using namespace std; int count=0; int p ...

  2. 深度学习之TensorFlow构建神经网络层

    深度学习之TensorFlow构建神经网络层 基本法 深度神经网络是一个多层次的网络模型,包含了:输入层,隐藏层和输出层,其中隐藏层是最重要也是深度最多的,通过TensorFlow,python代码可 ...

  3. tomcat启动时间过长的问题

    阿里云下的服务器安装jdk1.8和tomcat之后出现了一个问题,初次运行tomcat没有问题,可以正常访问tomcat首页,但是关闭之后再重启就发现tomcat首页刷不出来.而且再次关闭之后还报错了 ...

  4. [SDOI2011]染色

    [SDOI2011]染色 题目描述 输入输出格式 输出格式: 对于每个询问操作,输出一行答案. 解法 ps:这题本来是树剖的,但我用lct写的,以下是lct的写法,树剖会有所不同 我们考虑把不同色点的 ...

  5. Mysql 一次性备份导出/导入恢复所有数据库

    有木有遇到过这种情况?电脑或者服务器需要重装系统?可是你电脑上存着n多个网站的数据库,怎么办?把数据库文件夹拷贝出来,重装系统之后再拷回去?如果你使用了InnoDB引擎,恐怕那样做会出麻烦的,一个一个 ...

  6. 利用jmeter进行数据库测试

    1.首先,用jmeter进行数据库测试之前,要把oracle和mysql的JDBC驱动jar包放到jmeter安装路径的lib目录下,否则会提示错误 2.添加一个线程组,如下图 3.接下来添加一个JD ...

  7. 使用MVC5+Entity Framework6的Code First模式创建数据库并实现增删改查功能

    此处采用VS2017+SqlServer数据库 一.创建项目并引用dll: 1.创建一个MVC项目 2.采用Nuget安装EF6.1.3 二.创建Model 在models文件夹中,建立相应的mode ...

  8. NetFPGA-1G-CML Demo --- reference_router_nf1_cml

    环境 deepin 15.4 vivado 15.2 ise 14.6 前期准备 Github Wiki链接:https://github.com/NetFPGA/NetFPGA-public/wik ...

  9. HTTP协议以及HTTP2.0/1.1/1.0区别

    HTTP协议以及HTTP2.0/1.1/1.0区别 一.简介 摘自百度百科: 超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议.所 ...

  10. WPS怎么让前几页的页眉或者页脚与后面的不同

    其实不管利用WPS还是office对文档还是PPT进行操作,其实核心思想还是一种编程,主要是前端的编程,就是通过改变一些这些软件设置的样式,然后通过改变这些样式,使这些文字以老师要求的格式显示出来的, ...