es6复习之Promise详解以及相关的面试题

26 篇文章 1 订阅
12 篇文章 1 订阅

在我们开发的过程中,一般来说我们会碰到的回调嵌套都不会很多,一般就一到两级,但是某些情况下,回调嵌套很多时,代码就会非常繁琐,会给我们的编程带来很多的麻烦,这种情况俗称——回调地狱

Promise的含义

异步编程,回调地域的一种解决方案,比传统解决方案–回调函数和事件-更合理和更强大。
Promise对象具有以下两个特点。
1.对象的状态不受外界影响。Promise对象代表一个异步状态,有三种:pending(进行中),fulfilled(已成功)和rejected(已失败)。这也是Promise这个名字的由来,它的英文意思就是“承诺”,表示其他手段无法改变。
2.一旦状态改变,就不会再变,任何时候都可以得到这个结果。
状态改变:从pending变为fulfilled和从pending变为rejected
只要状态改变,此后就保持这个结果,称为resolved
状态已经发送改变了,再对Promise对象添加回调函数,也会立即得到这个结果。
(与事件不同,事件如果错过了再监听就得不到结果了)
有了Promise对象,就可以把异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。

Promise的用法

基本用法

// resolve,reject两个参数是js引擎提供的
// resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”
// reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”
const promise = new Promise(function (resolve, reject) {
  /*括号里判断异步操作是否成功*/
  if (true) {
    resolve(value);
  } else {
    reject(error);
  }
})

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

注意:Promise函数是同步的,但是Promise.then是异步的。

另一个Promise应用的例子,也是面试的常考题。
利用Promise封装一个ajax

//一个Promise实现ajax操作的例子
const getJSON = function (url) {
  const promise = new Promise(function (resolve, reject) {
    const handler = function () {
      if (this.readyState !== 4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    const client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.responseType = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();
  });
  return promise;
}

getJSON("/posts.json").then(function (json) {
  console.log('Contents:' + json);
}, function (error) {
  console.error('出错了', error);
})

Promise的原理

在Promise的内部,有一个状态管理器的存在,有三种状态:pending、fulfilled、rejected。(可以把promise看成状态机)

(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时的回调函数:

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

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

// resolve(成功) onFulfilled会被调用
const promise = new Promise((resolve, reject) => {
   resolve('fulfilled'); // 状态由 pending => fulfilled
});
promise.then(result => { // onFulfilled
    console.log(result); // 'fulfilled' 
}, reason => { // onRejected 不会被调用
})

// reject(失败) onRejected会被调用
const promise = new Promise((resolve, reject) => {
   reject('rejected'); // 状态由 pending => rejected
});
promise.then(result => { // onFulfilled 不会被调用
}, reason => { // onRejected 
    console.log(rejected); // 'rejected'
})

catch

 catch在链式写法中可以捕获前面then中发送的异常。
 fn = new Promise(function (resolve, reject) {
  let num = Math.ceil(Math.random() * 10)
  if (num > 5) {
    resolve(num)
  } else {
    reject(num)
  }
})
fn..then((res)=>{
  console.log(res)
}).catch((err)=>{
  console.log(`err=>${err}`)
})

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

  Promise.resolve 返回一个fulfilled状态的promise对象,Promise.reject 返回一个rejected状态的promise对象
  Promise.resolve('hello').then(function(value){
    console.log(value);
});

Promise.resolve('hello');
// 相当于
const promise = new Promise(resolve => {
   resolve('hello');
});

// reject反之

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

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

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

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

let p1 = new Promise((resolve)=>{
  setTimeout(()=>{
    console.log('1s') //1s后输出
    resolve(1)
  },1000)
})
let p10 = new Promise((resolve)=>{
  setTimeout(()=>{
    console.log('10s') //10s后输出
    resolve(10)
  },10000)
})
let p5 = new Promise((resolve)=>{
  setTimeout(()=>{
    console.log('5s') //5s后输出
    resolve(5)
  },5000)
})
Promise.all([p1, p10, p5]).then((res)=>{
    console.log(res); // 最后输出
})

这段代码运行时,根据看谁跑的慢的原则,则会在10s之后输出[1,10,5]。over,all收工。
race
 promise.race()方法也可以处理一个promise实例数组但它和promise.all()不同,从字面意思上理解就是竞速,那么理解起来上就简单多了,也就是说在数组中的元素实例那个率先改变状态,就向下传递谁的状态和异步结果。但是,其余的还是会继续进行的。

 let p1 = new Promise((resolve)=>{
  setTimeout(()=>{
    console.log('1s') //1s后输出
    resolve(1)
  },1000)
})
let p10 = new Promise((resolve)=>{
  setTimeout(()=>{
    console.log('10s') //10s后输出
    resolve(10) //不传递
  },10000)
})
let p5 = new Promise((resolve)=>{
  setTimeout(()=>{
    console.log('5s') //5s后输出
    resolve(5) //不传递
  },5000)
})
Promise.race([p1, p10, p5]).then((res)=>{
    console.log(res); // 最后输出
})

最终输入
1s
1
5s
10s
可以根据race这个属性做超时的操作(一个Promise写请求,一个Promise写计时器,用race得出最后结果)

/请求某个图片资源
let requestImg = new Promise(function(resolve, reject){
        var img = new Image();
        img.onload = function(){
            resolve(img);
        }
    });
//延时函数,用于给请求计时
let timeOut = new Promise(function(resolve, reject){
        setTimeout(function(){
            reject('图片请求超时');
        }, 5000);
    });

Promise.race([requestImg, timeout]).then((res)=>{
    console.log(res);
}).catch((err)=>{
    console.log(err);
});

总结

首先,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.

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

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

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

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
    reject('error')
  }, 1000)
})
promise.then((res)=>{
  console.log(res)
},(err)=>{
  console.log(err)
})

输出结果:success

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

setTimeout(()=>{
  console.log('setTimeout')
})
let p1 = new Promise((resolve)=>{
  console.log('Promise1')
  resolve('Promise2')
})
p1.then((res)=>{
  console.log(res)
})
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.

Promise.resolve(1)
    .then((res) => {
        console.log(res);
        return 2;
    })
    .catch((err) => {
        return 3;
    })
    .then((res) => {
        console.log(res);
    }); 

输出结果:1 2

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

const promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('开始');
resolve('success');
}, 5000);
});
 
const start = Date.now();
promise.then((res) => {
console.log(res, Date.now() - start);
});
 
promise.then((res) => {
console.log(res, Date.now() - start);
});

输出结果:

开始

success 5002

success 5002

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

let p1 = new Promise((resolve,reject)=>{
  let num = 6
  if(num<5){
    console.log('resolve1')
    resolve(num)
  }else{
    console.log('reject1')
    reject(num)
  }
})
p1.then((res)=>{
  console.log('resolve2')
  console.log(res)
},(rej)=>{
  console.log('reject2')
  let p2 = new Promise((resolve,reject)=>{
    if(rej*2>10){
      console.log('resolve3')
      resolve(rej*2)
    }else{
      console.log('reject3')
      reject(rej*2)
    }
  })
  return p2
}).then((res)=>{
  console.log('resolve4')
  console.log(res)
},(rej)=>{
  console.log('reject4')
  console.log(rej)
})

输出结果:

reject1
    reject2
    resolve3
    resolve4
    12

解题思路:我们上面说了Promise的先进之处在于可以在then方法中继续写Promise对象并返回。
8.重头戏!!!!实现一个简单的Promise
/

/重头戏,实现一个简单的Promise
function Promise(fn) {
  var status = 'pending'
  function successNotify() {
    status = 'fulfiled'   //状态变为fullfiled
    toDoThen.apply(undefined, arguments)//执行回调
  }

  function failNoitfy() {
    status = 'rejected'        //状态变为rejected
    toDoThen.apply(undefined, arguments)  //执行回调
  }

  function toDoThen() {
    setTimeout(() => {   //保证回调是异步执行的
      if (status === 'fulfillled') {
        for (let i = 0; i < successArray.length; i++) {
          successArray[i].apply(undefined, arguments)
        }
      } else if (status === 'rejected') {
        for (let i = 0; i < failArray.length; i++) {
          failArray[i].apply(undefined, arguments)
        }
      }

    })
  }
  var successArray = []
  var failArray = []
  fn.call(undefined, successNotify, failNoitfy)
  return {
    then: function (successFn, failFn) {
      successArray.push(successFn)
      failArray.push(failFn)
      return undefined  //此处应返回一个Promise
    }
  }
}

网易关于Promise笔试题目

公司放映系统最近要上线一个『预定随机推荐电影』功能,每天用户通过系统预定名额,由系统每日推荐一部电影,按时推送到用户。现在,在系统已有如下异步方法封装的前提下
• getTodayUsers ( callback ): 获取今日预定的用户id列表,使用如下 getTodyUsers(userIds=>{ console.log(userIds )}), 即回调中拿到用户id列表
• getTodayMovie(callback): 获取今日推荐的电影id, 使用如下 getTodayMovie( movieId=> {console.log(movieId )}) ,即回调中拿到今日的电影id
• bookMovieForUsers(userIds, movieId, callback): 使用用户id列表预定某部电影,使用如下bookMovieForUsers([1,2,3], 1000, ()=>{console.log(‘预定成功了’)})
请封装一个bookTodayMovieForTodayUser()的方法,它的作用是为今天预定的用户订阅今天系统推荐的电影。它返回一个promise, 这个promise在请求后被resolve. 使用方式如下
bookTodayMovieForTodayUser().then( ()=> console.log('预定成功’) )
注: 简单起见,所有情况都不需要考虑失败情况

function bookTodayMovieForTodayUser() {
    let u = new Promise((resolve, reject) => {
        getTodyUsers(userIds => {
            resolve(userIds)
        })
    })
    let m = new Promise((resolve, reject) => {
        getTodayMovie(movieId => {
            resolve(movieId)
        })
    })
    return Promise.all([u, p]).then((result) => {
        bookMovieForUsers(...result, resolve);
    })
}

其他面试题目
https://blog.csdn.net/FE_dev/article/details/83278508
https://www.cnblogs.com/dojo-lzz/p/5495671.html
文章引用:
http://es6.ruanyifeng.com/#docs/promise#Promise-prototype-then
https://www.cnblogs.com/lunlunshiwo/p/8852984.html

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值