前言
前端js学习中,让大家最难受的就是异步的问题,解决异步、回调地狱等问题时你必须得学会promise,对于多数前端程序员来说promise简直就是噩梦,本篇文章就是从通俗易懂的角度做为切入点,帮助大家轻松掌握promise
异步编程
想要学习promise,你必须要懂得什么是异步编程!众所周知,js语言是单线程机制。所谓单线程就是按次序执行,执行完一个任务再执行下一个。但是不影响存在同步和异步的两种操作,这两种操作做事情其实都是在一条流水线上(单线程),只是这两种操作在单线程上的执行顺序不一样罢了!当js触发到异步任务时,会将异步任务交给浏览器处理,当执行有结果时,会把异步任务的回调函数插入待处理队列的队尾!
我们通俗的去解释一下我们的异步:异步就是从主线程发射一个子线程来完成任务,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。
该图摘自于菜鸟教程中的异步编程小节,帮助大家更好的理解什么是异步!
回调函数
回调函数的定义非常简单:一个函数被当做一个实参传入到另一个函数(外部函数),并且这个函数在外部函数内被调用,用来完成某些任务的函数。就称为回调函数
回调函数的两种写法(实现效果相同):
const text = () => {
document.write('hello may')
}
setTimeout(text,1000)
setTimeout(()=>{
document.write("hello may")
},1000)
这段代码中的 setTimeout 就是一个消耗时间较长的过程,它的第一个参数是个回调函数,第二个参数是毫秒数,这个函数执行之后会产生一个子线程,子线程会等待 1 秒,然后执行回调函数 “text”,在文本中输出hello may。
setTimeout会在子线程中等待1秒,但是主线程的运行不会受到影响!例如以下代码:
setTimeout(()=>{
document.write("hello davis")
},1000)
console.log('123456');
回调地狱
回调地狱这个词听起来就非常的高大上,想要接触Promise之前,必须要懂得什么是回调地狱,以及为什么会产生回调地狱?先来看看概念:当一个回调函数嵌套一个回调函数的时候就会出现一个嵌套结构当嵌套的多了就会出现回调地狱的情况。
举个例子:
比如我们发送三个 ajax 请求:
- 第一个正常发送
- 第二个请求需要第一个请求的结果中的某一个值作为参数
- 第三个请求需要第二个请求的结果中的某一个值作为参数
请看以下代码:
$.ajax({
url: '我是第一个请求',
type: 'get',
success (res) {
// 现在发送第二个请求
$.ajax({
url: '我是第二个请求',
type:'post',
data: { a: res.a, b: res.b },
success (res1) {
// 进行第三个请求
$.ajax({
url: '我是第三个请求',
type:'post',
data: { a: res1.a, b: res1.b },
success (res2) {
console.log(res2)
}
})
}
})
}
})
这种代码看起来属实是折磨人啊!当我们把代码写成这样的时候,就陷入了可维护性差的状态了,代码体验非常的不良好,看一会就给看懵了,为了解决这个问题,于是,就引入了我们的Promise,用Promise去解决回调地狱问题!
Promise
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大,它是一个 ECMAScript 6 提供的类,目的是更加优雅地书写复杂的异步任务。
Promise对象有以下两个特点:
- 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
Promise语法格式
new Promise(function (resolve, reject) {
// resolve 表示成功的回调
// reject 表示失败的回调
}).then(function (res) {
// 成功的函数
}).catch(function (err) {
// 失败的函数
})
出现了new关键字,就明白了Promise对象其实就是一个构造函数,是用来生成Promise实例的。能看出来构造函数接收了一个函数作为参数,该函数就是Promise构造函数的回调函数,该函数中有两个参数resolve和reject,这两个参数也分别是两个函数!
简单的去理解的话resolve函数的目的是将Promise对象状态变成成功状态,在异步操作成功时调用,将异步操作的结果,作为参数传递出去。reject函数的目的是将Promise对象的状态变成失败状态,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
代码示例:
const promise = new Promise((resolve,reject)=>{
//异步代码
setTimeout(()=>{
// resolve(['111','222','333'])
reject('error')
},2000)
})
promise.then((res)=>{
//兑现承诺,这个函数被执行
console.log('success',res);
}).catch((err)=>{
//拒绝承诺,这个函数就会被执行
console.log('fail',err);
})
Promise链式
then方法返回的是一个新的Promise实例(注意:不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
我想要实现在一个数组中查看一个帖子,但是我最终的目的是得到这个帖子下面的所有评论,这该怎么实现呢?
代码如下:
ajax({
url:"http://localhost:3000/news",
data : {
author : "may"
}
}).then(res=>{
return pajax({
url : "http://localhost:3000/comments",
data : {
newsId : res[0].id
}
})
}).then(res=>{
console.log(res);
}).catch(err=>{
console.log(err);
})
这里使用了一个Promise已经封装过的ajax,我们从第一个接口中得到了帖子id,然后在then中的函数发送第二个请求(携带了第一个请求返回过来的参数),我们最后想要拿到第二个接口的结果,于是又有了一个then方法,但是在第一个then方法中要把一个新的Promise实例return出去,这样的话,第二个then才起作用!
小结
不论是在前端的项目开发中还是在前端的面试过程中,Promise的地位就是举足轻重的,虽然解决异步编程的终极解决方案是async和await,但是它们也是基于Promise封装而来的。