Javascript의 Promise를 철저하게 이해
ES6原生提供了 Promise 对象。
到底是何方妖怪呢?打出来看看:
所谓 Promise,就是一个对象,用来传递异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的 API,可供进一步处理。
Promise 对象有以下两个特点。
(1)对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和 Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是「承诺」,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。
Promise 也有一些缺点。首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
废话不多说,直接上demo:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Promise 学习笔记</title> <script type="text/javascript"> window.onload = function() { function pms1() { return new Promise(function(resolve, reject) { setTimeout(function() { console.log('执行任务1'); resolve('执行任务1成功'); }, 2000); }); } function pms2() { return new Promise(function(resolve, reject) { setTimeout(function() { console.log('执行任务2'); resolve('执行任务2成功'); }, 2000); }); } function pms3() { return new Promise(function(resolve, reject) { setTimeout(function() { console.log('执行任务3'); resolve('执行任务3成功'); }, 2000); }); } pms1().then(function(data) { console.log('第1个回调:' + data); return pms2(); }) .then(function(data) { console.log('第2个回调:' + data); return pms3(); }) .then(function(data) { console.log('第3个回调:' + data); return '还没完!该结束了吧!' }).then(function(data) { console.log(data); }); } </script> </head> <body> </body> </html>
怎么样?是不是灰常简单啊!
demo2:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <script type="text/javascript"> window.onload = function() { function pms1() { return new Promise(function(resolve, reject) { setTimeout(function() { var num = Math.ceil(Math.random() * 10); //生成1-10的随机数 if(num <= 5) { resolve(num); } else { reject('数字太大了吧!'); } }, 2000); }); } setInterval(function() { pms1().then(function(data) { //小于等于5的 console.log(data); }, function(data) { //大于的 console.log(data); }) }, 1000); } </script> </head> <body> </body> </html>
Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 方法和 reject 方法。
如果异步操作成功,则用 resolve 方法将 Promise 对象的状态,从「未完成」变为「成功」(即从 pending 变为 resolved);
如果异步操作失败,则用 reject 方法将 Promise 对象的状态,从「未完成」变为「失败」(即从 pending 变为 rejected)。
all的用法:
demo:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Promise 学习笔记</title> <script type="text/javascript"> window.onload = function() { function pms1() { return new Promise(function(resolve, reject) { setTimeout(function() { console.log('执行任务1'); resolve('执行任务1成功'); }, 2000); }); } function pms2() { return new Promise(function(resolve, reject) { setTimeout(function() { console.log('执行任务2'); resolve('执行任务2成功'); }, 2000); }); } function pms3() { return new Promise(function(resolve, reject) { setTimeout(function() { console.log('执行任务3'); resolve('执行任务3成功'); }, 2000); }); } Promise.all([pms1(), pms2(), pms3()]).then(function(data) { console.log(data); console.log({}.toString.call(data)); }) } </script> </head> <body> </body> </html>
用Promise.all来执行,all接收一个数组参数,里面的值最终都算返回Promise对象。这样,三个异步操作的并行执行的,等到它们都执行完后才会进到then里面。那么,三个异步操作返回的数据哪里去了呢?都在then里面呢,all会把所有异步操作的结果放进一个数组中传给then,就是上面的results。
race的用法
all方法的效果实际上是「谁跑的慢,以谁为准执行回调」,那么相对的就有另一个方法「谁跑的快,以谁为准执行回调」,这就是race方法,这个词本来就是赛跑的意思。race的用法与all一样,我们把上面runAsync1的延时改为1秒来看一下:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Promise 学习笔记</title> <script type="text/javascript"> window.onload = function() { function pms1() { return new Promise(function(resolve, reject) { setTimeout(function() { console.log('执行任务1'); resolve('执行任务1成功'); }, 1000); }); } function pms2() { return new Promise(function(resolve, reject) { setTimeout(function() { console.log('执行任务2'); resolve('执行任务2成功'); }, 2000); }); } function pms3() { return new Promise(function(resolve, reject) { setTimeout(function() { console.log('执行任务3'); resolve('执行任务3成功'); }, 3000); }); } Promise.race([pms1(), pms2(), pms3()]).then(function(data) { console.log(data); //注意上面的延时时间 }) } </script> </head> <body> </body> </html>
看到没:只有第一个执行了回调!
在then里面的回调开始执行时,runAsync2()和runAsync3()并没有停止,仍旧再执行。于是再过1秒后,输出了他们结束的标志。
这个race有什么用呢?使用场景还是很多的,比如我们可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作。
再来看看jquery里面的$.Deferred:
jquery用$.Deferred实现了Promise规范,$.Deferred是个什么玩意呢?还是老方法,打印出来看看,先有个直观印象:
var def = $.Deferred(); console.log(def);
输出如下:
$.Deferred()返回一个对象,我们可以称之为Deferred对象,上面挂着一些熟悉的方法如:done、fail、then等。jquery就是用这个Deferred对象来注册异步操作的回调函数,修改并传递异步操作的状态。
Deferred对象的基本用法如下,为了不与ajax混淆,我们依旧举setTimeout的例子:
<!doctype html> <html> <head> <meta charset="UTF-8" /> <title>Document</title> <script type="text/javascript" src="js/jquery-3.1.1.min.js"></script> <script type="text/javascript"> $(function() { function runAsync() { var def = $.Deferred(); setTimeout(function() { console.log('执行完成'); def.resolve('随便什么数据'); }, 2000); return def; } runAsync().then(function(data) { console.log(data) }); }) </script> </head> <body> </body> </html>
在runAsync函数中,我们首先定义了一个def对象,然后进行一个延时操作,在2秒后调用def.resolve(),最后把def作为函数的返回。调用runAsync的时候将返回def对象,然后我们就可以.then来执行回调函数。
是不是感觉和ES6的Promise很像呢?
区别在何处一看便知。由于jquery的def对象本身就有resolve方法,所以我们在创建def对象的时候并未像ES6这样传入了一个函数参数,是空的。在后面可以直接def.resolve()这样调用。
<!doctype html> <html> <head> <meta charset="UTF-8" /> <title>Document</title> <script type="text/javascript" src="js/jquery-3.1.1.min.js"></script> <script type="text/javascript"> $(function() { function runAsync() { var def = $.Deferred(); setTimeout(function() { console.log('执行完成'); def.resolve('随便什么数据'); }, 2000); return def; } var pms=runAsync(); pms.then(function(data) { console.log(data) }); pms.resolve('我穿越了!') }) </script> </head> <body> </body> </html>
这样也有一个弊端,因为执行runAsync()可以拿到def对象,而def对象上又有resolve方法,那么岂不是可以在外部就修改def的状态了?比如我把上面的代码修改如下:
现象会如何呢?并不会在2秒后输出“执行完成”,而是直接输出“我穿越了”。因为我们在异步操作执行完成之前,没等他自己resolve,就在外部给resolve了。这显然是有风险的,比如你定义的一个异步操作并指定好回调函数,有可能被别人给提前结束掉,你的回调函数也就不能执行了。
怎么办?jquery提供了一个promise方法,就在def对象上,他可以返回一个受限的Deferred对象,所谓受限就是没有resolve、reject等方法,无法从外部来改变他的状态,用法如下:
<!doctype html> <html> <head> <meta charset="UTF-8" /> <title>Document</title> <script type="text/javascript" src="js/jquery-3.1.1.min.js"></script> <script type="text/javascript"> $(function() { function runAsync() { var def = $.Deferred(); setTimeout(function() { console.log('执行完成'); def.resolve('随便什么数据'); }, 2000); return def.promise(); } var pms=runAsync(); pms.then(function(data) { console.log(data) }); //pms.resolve('我穿越了!'); //这一句会报错jquery-3.1.1.min.js:2 Uncaught TypeError: pms.resolve is not a function }) </script> </head> <body> </body> </html>
then的链式调用 既然Deferred也是Promise规范的实现者,那么其他特性也必须是支持的。链式调用的用法如下:
<!doctype html> <html> <head> <meta charset="UTF-8" /> <title>Document</title> <script type="text/javascript" src="js/jquery-3.1.1.min.js"></script> <script type="text/javascript"> $(function() { function runAsync() { var def = $.Deferred(); setTimeout(function() { console.log('执行完成'); def.resolve('随便什么数据'); }, 2000); return def.promise(); } var pms = runAsync(); pms.then(function(data) { console.log('1:' + data); return runAsync(); }) .then(function(data) { console.log('2:' + data); return runAsync(); }) .then(function(data) { console.log('3:' + data); }); //pms.resolve('我穿越了!'); //这一句会报错jquery-3.1.1.min.js:2 Uncaught TypeError: pms.resolve is not a function }) </script> </head> <body> </body> </html>
done与fail
我们知道,Promise规范中,then方法接受两个参数,分别是执行完成和执行失败的回调,而jquery中进行了增强,还可以接受第三个参数,就是在pending状态时的回调,如下:
deferred.then( doneFilter [, failFilter ] [, progressFilter ] )
除此之外,jquery还增加了两个语法糖方法,done和fail,分别用来指定执行完成和执行失败的回调,也就是说这段代码:
d.then(function(){ console.log('执行完成'); }, function(){ console.log('执行失败'); });
与这段代码是等价的:
d.done(function(){ console.log('执行完成'); }) .fail(function(){ console.log('执行失败'); });
always的用法
jquery的Deferred对象上还有一个always方法,不论执行完成还是执行失败,always都会执行,有点类似ajax中的complete。不赘述了。
$.when的用法
jquery中,还有一个$.when方法来实现Promise,与ES6中的all方法功能一样,并行执行异步操作,在所有的异步操作执行完后才执行回调函数。不过$.when并没有定义在$.Deferred中,看名字就知道,$.when,它是一个单独的方法。与ES6的all的参数稍有区别,它接受的并不是数组,而是多个Deferred对象,如下:
$.when(runAsync(), runAsync2(), runAsync3()) .then(function(data1, data2, data3){ console.log('全部执行完成'); console.log(data1, data2, data3); });
jquery中没有像ES6中的race方法吗?就是以跑的快的为准的那个方法。对的,jquery中没有。
以上就是jquery中Deferred对象的常用方法了,还有一些其他的方法用的也不多,干脆就不记它了。接下来该说说ajax了。
ajax与Deferred的关系
jquery的ajax返回一个受限的Deferred对象,还记得受限的Deferred对象吧,也就是没有resolve方法和reject方法,不能从外部改变状态。想想也是,你发一个ajax请求,别人从其他地方给你取消掉了,也是受不了的。
既然是Deferred对象,那么我们上面讲到的所有特性,ajax也都是可以用的。比如链式调用,连续发送多个请求:
req1 = function(){ return $.ajax(/*...*/); } req2 = function(){ return $.ajax(/*...*/); } req3 = function(){ return $.ajax(/*...*/); } req1().then(req2).then(req3).done(function(){ console.log('请求发送完毕'); });
明白了ajax返回对象的实质,那我们用起来就得心应手了。
success、error与complete
这三个方法或许是我们用的最多的,使用起来是这样的:
$.ajax(/*...*/) .success(function(){/*...*/}) .error(function(){/*...*/}) .complete(function(){/*...*/})
分别表示ajax请求成功、失败、结束的回调。这三个方法与Deferred又是什么关系呢?其实就是语法糖,success对应done,error对应fail,complete对应always,就这样,只是为了与ajax的参数名字上保持一致而已,更方便大家记忆,看一眼源码:
deferred.promise( jqXHR ).complete = completeDeferred.add; jqXHR.success = jqXHR.done; jqXHR.error = jqXHR.fail;
complete那一行那么写,是为了减少重复代码,其实就是把done和fail又调用一次,与always中的代码一样。deferred.promise( jqXHR )这句也能看出,ajax返回的是受限的Deferred对象。
jquery加了这么些个语法糖,虽然上手门槛更低了,但是却造成了一定程度的混淆。一些人虽然这么写了很久,却一直不知道其中的原理,在面试的时候只能答出一些皮毛,这是很不好的。这也是我写这篇文章的缘由。
看一个promise.js库:
/*! * Promise 자바스크립트 라이브러리 v2.0.0 */ ; (함수(창) { var _promise = 함수(then) { this.then = 그다음 || []; this.state = ""; this._상수 = { 모두: "아무거나", 번호: "번호", 해결됨: "해결됨", 거부됨: "거부됨", 보류 중: '보류 중' }; }; _promise.prototype = { 해결: function() { if(this.state == this._CONSTANT.pending) { this.state = this._CONSTANT.resolved; 반품; } if(this.state !== "") return; if(this.promiseArr) { for(var i = 0, j = this.promiseArr.length; i 1) { 반품; } } } this.state = this._CONSTANT.resolved; if(!this.thens) return; if(this.thens[0] && this.thens[0].finallyCB) this.thens[0].finallyCB.apply(null, arguments); var t, n; while(t = this.thens.shift()) { if(typeof t === this._CONSTANT.number) { var self = 이것; setTimeout(함수() { var prms = new _promise(self.thens); prms.resolve(); }, t); 부서지다; } var doneFn = t.done, 액션 = t.action; if(!doneFn) 계속; if(doneFn 인스턴스배열) { var arr = []; for(var i = 0, j = doneFn.length; i

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











JavaScript 문자열 교체 방법 및 FAQ에 대한 자세한 설명 이 기사는 JavaScript에서 문자열 문자를 대체하는 두 가지 방법 인 내부 JavaScript 코드와 웹 페이지의 내부 HTML을 탐색합니다. JavaScript 코드 내부의 문자열을 교체하십시오 가장 직접적인 방법은 대체 () 메소드를 사용하는 것입니다. str = str.replace ( "find", "replace"); 이 메소드는 첫 번째 일치 만 대체합니다. 모든 경기를 교체하려면 정규 표현식을 사용하고 전역 플래그 g를 추가하십시오. str = str.replace (/fi

이 튜토리얼은 사용자 정의 Google 검색 API를 블로그 또는 웹 사이트에 통합하는 방법을 보여 주며 표준 WordPress 테마 검색 기능보다보다 세련된 검색 경험을 제공합니다. 놀랍게도 쉽습니다! 검색을 Y로 제한 할 수 있습니다

그래서 여기 당신은 Ajax라는이 일에 대해 배울 준비가되어 있습니다. 그러나 정확히 무엇입니까? Ajax라는 용어는 역동적이고 대화식 웹 컨텐츠를 만드는 데 사용되는 느슨한 기술 그룹을 나타냅니다. 원래 Jesse J에 의해 만들어진 Ajax라는 용어

이 기사 시리즈는 2017 년 중반에 최신 정보와 새로운 예제로 다시 작성되었습니다. 이 JSON 예에서는 JSON 형식을 사용하여 파일에 간단한 값을 저장하는 방법을 살펴 봅니다. 키 값 쌍 표기법을 사용하여 모든 종류를 저장할 수 있습니다.

코드 프레젠테이션 향상 : 개발자를위한 10 개의 구문 하이 라이터 웹 사이트 나 블로그에서 코드 스 니펫을 공유하는 것은 개발자에게 일반적인 관행입니다. 올바른 구문 형광펜을 선택하면 가독성과 시각적 매력을 크게 향상시킬 수 있습니다. 티

손쉬운 웹 페이지 레이아웃에 대한 jQuery 활용 : 8 에센셜 플러그인 jQuery는 웹 페이지 레이아웃을 크게 단순화합니다. 이 기사는 프로세스를 간소화하는 8 개의 강력한 JQuery 플러그인을 강조합니다. 특히 수동 웹 사이트 생성에 유용합니다.

이 기사는 JavaScript 및 JQuery Model-View-Controller (MVC) 프레임 워크에 대한 10 개가 넘는 튜토리얼을 선별 한 것으로 새해에 웹 개발 기술을 향상시키는 데 적합합니다. 이 튜토리얼은 Foundatio의 다양한 주제를 다룹니다

핵심 포인트 JavaScript에서는 일반적으로 메소드를 "소유"하는 객체를 말하지만 함수가 호출되는 방식에 따라 다릅니다. 현재 객체가 없으면 글로벌 객체를 나타냅니다. 웹 브라우저에서는 창으로 표시됩니다. 함수를 호출 할 때 이것은 전역 객체를 유지하지만 객체 생성자 또는 그 메소드를 호출 할 때는 객체의 인스턴스를 나타냅니다. call (), apply () 및 bind ()와 같은 메소드를 사용 하여이 컨텍스트를 변경할 수 있습니다. 이 방법은 주어진이 값과 매개 변수를 사용하여 함수를 호출합니다. JavaScript는 훌륭한 프로그래밍 언어입니다. 몇 년 전,이 문장은있었습니다
