프론트엔드 js 학습에서 누구에게나 가장 불편한 점은 비동기 문제, 콜백 지옥 등의 문제를 해결하려면 대부분 Promise를 배워야 합니다. 프론트엔드 프로그래머에게 약속은 악몽일 뿐입니다. 이 글은 모든 사람이 약속을 쉽게 익히는 데 도움이 되는 시작점으로서 이해하기 쉬운 관점에서 작성되었습니다异步、回调地狱
等问题时你必须得学会promise,对于多数前端程序员来说promise简直就是噩梦,本篇文章就是从通俗易懂的角度做为切入点,帮助大家轻松掌握promise
想要学习promise,你必须要懂得什么是异步编程!众所周知,js语言是单线程
机制。所谓单线程就是按次序执行,执行完一个任务再执行下一个。但是不影响存在同步
和异步
的两种操作,这两种操作做事情其实都是在一条流水线上(单线程),只是这两种操作在单线程上的执行顺序不一样罢了!当js触发到异步任务时,会将异步任务交给浏览器处理,当执行有结果时,会把异步任务的回调函数插入待处理队列的队尾!
我们通俗的去解释一下我们的异步:异步就是从主线程发射一个子线程来完成任务
,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数
,后一个任务则是不等前一个任务结束就执行
,所以程序的执行顺序与任务的排列顺序是不一致的、异步的.
该图摘自于菜鸟教程中的
异步编程
小节,帮助大家更好的理解什么是异步!
回调函数的定义非常简单:一个函数被当做一个
实参
传入到另一个函数(外部函数
),并且这个函数在外部函数内被调用,用来完成某些任务的函数。就称为回调函数
回调函数的两种写法(实现效果相同
):
const text = () => { document.write('hello james') } setTimeout(text,1000)
setTimeout(()=>{ document.write("hello james") },1000)
这段代码中的 setTimeout
就是一个消耗时间较长的过程,它的第一个参数是个回调函数
,第二个参数是毫秒数
,这个函数执行之后会产生一个子线程,子线程会等待 1 秒,然后执行回调函数 "text"
,在文本中输出hello james
setTimeout会在子线程中等待1秒,但是主线程的运行不会受到影响!例如以下代码:
setTimeout(()=>{ document.write("hello davis") },1000) console.log('123456');
在这里会先打印出来
123456
(主线程
),然后一秒后在文本中输出hello davis
(子线程
)
回调地狱这个词听起来就非常的高大上,想要接触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
是异步编程的一种解决方案
,比传统的解决方案——回调函数和事件——更合理和更强大,它是一个 ECMAScript 6 提供的类
,目的是更加优雅地书写复杂的异步任务
。
Promise对象有以下两个特点:
对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态
。这也是Promise这个名字的由来,它的英语意思就是“承诺”
단일 스레드
메커니즘입니다. 소위 단일 스레드는 다음 작업을 실행하기 전에 하나의 작업을 실행하는 순서대로 실행되는 것을 의미합니다. 그러나 동기
및 비동기
라는 두 작업의 존재에는 영향을 미치지 않습니다. 이 두 작업은 실제로 어셈블리 라인(단일 스레드)에서 작업을 수행하지만 이 두 작업은 단일 스레드에서의 실행 순서는 다릅니다! js가 비동기 작업을 트리거하면 비동기 작업이 처리를 위해 브라우저로 넘겨집니다. 실행 결과가 나오면 비동기 작업의 콜백 함수가 처리할 대기열 끝에 삽입됩니다. 비동기는 작업을 완료하기 위해 메인 스레드에서 하위 스레드를 시작하는 것입니다
. 각 작업에는 하나 이상의 콜백 함수(콜백)가 있습니다. 이전 작업이 끝난 후 다음 작업을 실행하는 대신 콜백 함수가 실행됩니다
. 이전 작업이 끝나기 전에 후자의 작업이 실행되므로
프로그램 순서가 작업 순서와 일관되지 않고 비동기적입니다.🎜🎜🎜🎜이 그림은 초보 튜토리얼의🎜콜백 함수를 작성하는 두 가지 방법(비동기 프로그래밍<에서 발췌한 것입니다. /code> 섹션을 통해 모두가 비동기식의 정의를 더 잘 이해할 수 있도록 돕습니다. 🎜</blockquote><h2><a id="_27"></a><strong>콜백 함수</strong></h2><hr/><blockquote>🎜콜백 함수의 정의는 매우 간단합니다. : 함수 다른 함수(<code>외부 함수
)에실제 매개변수
로 전달되는 함수이며, 이 함수는 특정 작업을 완료하기 위해 외부 함수 내에서 호출됩니다.콜백 함수
라고 합니다🎜
구현 효과는 동일
): 🎜new Promise(function (resolve, reject) { // resolve 表示成功的回调 // reject 表示失败的回调 }).then(function (res) { // 成功的函数 }).catch(function (err) { // 失败的函数 })
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); })
setTimeout< in 이 코드 /code>는 오랜 시간이 걸리는 프로세스입니다. 첫 번째 매개변수는 <code>콜백 함수
이고, 두 번째 매개변수는 이 함수가 실행된 후입니다. 하위 스레드는 1초 동안 기다린 후 콜백 함수 "text"
를 실행하고 텍스트에 hello james
를 출력합니다. 하위 스레드에서 1초 동안 기다리지만 메인 스레드의 작업에는 영향을 미치지 않습니다. 예를 들어 다음 코드는 🎜pajax({ url:"http://localhost:3000/news", data : { author : "james" } }).then(res=>{ return pajax({ url : "http://localhost:3000/comments", data : { newsId : res[0].id } }) }).then(res=>{ console.log(res); }).catch(err=>{ console.log(err); })
🎜여기서123456
(메인 스레드
)가 먼저 인쇄된 다음hello davis<가 출력됩니다. 1초 후 텍스트에 /code>(<code>하위 스레드
)🎜
콜백 지옥
이 무엇인지, 그리고 왜 콜백 지옥이 발생하는지 이해해야 합니다. 콜백 함수가 콜백 함수를 중첩하면 중첩된 구조가 나타납니다. 중첩된 함수가 너무 많으면 콜백 지옥이 발생합니다
. const p = Promise.all([p1, p2, p3]);
🎜이런 종류의 코드는 정말 괴로워 보입니다! 이렇게 코드를 작성하게 되면유지보수성이 좋지 않은
상태에 빠지게 됩니다. 코드 경험이 매우 좋지 않아 한동안 읽어보고 나면 이 문제를 해결하기 위해 혼란을 느끼게 됩니다.Promise
를 도입하고 Promise를 사용하여 콜백 지옥 문제를 해결했습니다! 🎜
🎜Promise
는 asynchronous의 일부입니다. 프로그래밍 기존 솔루션보다 더 합리적이고 강력한 솔루션
- 콜백 함수 및 이벤트는 ECMAScript 6
에서 제공하는 클래스로, 를 더욱 우아하게 작성하는 것을 목표로 합니다. 비동기 작업
. 보류 중
(진행 중), 이행
(성공) 및 거부
(실패)의 세 가지 상태를 갖습니다. 비동기 작업의 결과만 현재 상태를 결정할 수 있으며 다른 작업은 이 상태를 변경할 수 없습니다
. 이것이 Promise라는 이름의 유래이기도 합니다. 영어 의미는 "commitment"
이며, 이는 다른 방법으로 변경할 수 없다는 의미입니다. 🎜一旦状态改变,就不会再变,任何时候都可以得到这个结果
。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
两个特点摘自于??阮一峰ES6文章
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是一个构造函数,new之后等于说调用了构造函数,构造函数中传的参数是一个函数,这个函数内的两个参数分别又是两个函数(
reslove
和reject
),虽然感觉很绕,但是理清思路会很清晰的!我们得到对象
promise
,promise对象中自带有两个方法then
和catch
,这两个方法中会分别再传入一个回调函数,这个回调函数的目的在于返回你所需要成功或失败的信息!那么怎么去调用这两个回调函数呢?
看下方图可以快速理解:
这两个函数分别做为参数(reslove
和reject
)传到上方的函数中去了.随后在构造函数的回调函数中写入异步代码
(例如:ajax
和定时器
),这里使用了定时器作为例子,如果你想表达的是成功回调,你可以在内部调用函数reslove('一般情况下是后端返回的成功数据)
。如果你想表达的是失败回调,你可以调用reject('一般情况下是后端返回的失败信息')
.
这些就是Promise执行的过程!虽然理解着很绕,但是多读几遍绝对有不一样的收获!
then
方法返回的是一个新的Promise实例
(注意:不是原来那个Promise实例
)。因此可以采用链式写法
,即then方法后面再调用另一个then方法
实际案例:
我想要实现在一个数组中查看一个帖子,但是我最终的目的是得到这个帖子下面的所有评论,这该怎么实现呢?
实现思路:
先从一个接口中获取这个帖子的信息,然后通过该帖子的帖子id
从而获取到该帖子下的所有评论
代码如下:
pajax({ url:"http://localhost:3000/news", data : { author : "james" } }).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才起作用!(这是因为then
方法是Promise 实例
所具有的方法,也就是说,then方法是定义在原型对象Promise.prototype上的
)====>我们可以打印一下:console.log(Promise.prototype)
可以看的出来原型对象Promise.prototype
中是有then方法的!
Promise.all()
Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例
。
语法格式:
const p = Promise.all([p1, p2, p3]);
Promise.all()
方法接受一个数组
作为参数,p1、p2、p3都是 Promise 实例
,如果不是,就会调用Promise.reslove() [该方法可自行了解]
自动将参数转为 Promise 实例,再进一步处理。
说那么多白话没用,我们可以根据一个案例,就可以明白Promise.all()
的用途了。
实际案例:
如果你想实现一个效果:在一个页面中,等到页面中所有的请求返回数据后,再渲染页面,该怎么实现呢?(在实际开发中我们会看到loading
加载页面,等数据返回完后,loading加载页面会消失,整个页面就展现出来了,增强用户的体验。)
实现思路:
通过Promise.all()
方法,等多个接口全部接收到数据后,再统一进行处理,然后渲染页面
代码如下:
console.log("显示加载中") const q1 = pajax({ url:"http://localhost:3000/looplist" }) const q2 = pajax({ url:"http://localhost:3000/datalist" }) Promise.all([q1,q2]).then(res=>{ console.log(res) console.log("隐藏加载中...") }).catch(err=>{ console.log(err) })
代码分析:
在上方代码中,全局打印
显示加载中
是代替loading的页面,表示该页面现在正是loading
页面中,等到q1
和q2
所请求接口都得到返回的信息后,在then
方法中接收收据,并且可以进行渲染页面,同时隐藏了loading
加载页面!
不论是在前端的项目开发中还是在前端的面试过程中,Promise的地位就是举足轻重的
,虽然解决异步编程的终极解决方案是async和await
,但是它们也是基于Promise封装而来的,在以往文章中,我就说过,学习编程重要的是搞懂某个技术是怎么实现的,而不是做一个cv侠
,多去思考,才能进步。继续加油吧,少年!
【相关推荐:javascript视频教程、编程基础视频】
위 내용은 하나의 기사는 Promise를 쉽게 익히는 데 도움이 될 것입니다의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!