Promise定义
Promise是CommonJs的规范之一,包含resolve,reject,done,fail,then等方法,能够帮助我们控制代码的流程,避免函数的多层嵌套。异步在web开发中越来越重要,对于开发人员来说,J非线性执行的编程会让开发者觉得难以掌控,而Promise可以让我们更好地掌控代码的执行流程,jQuery等流行的js库都已经实现了这个对象,现在ES6已经原生实现了Promise。
场景说明:
有时候会遇到这样的情况,需要发两个异步请求,而第二个请求需要用到第一个返回结果的数据:
ajax({ url: url1, success: function(data) { ajax({ url: url2, data: data, success: function() { } }); } });
稍微整理一下,把后一个调用封装成一个函数,就是酱紫滴:
function A() { ajax({ url: url1, success: function(data) { B(data); } }); } function B(data) { ajax({ url: url2, success: function(data) { ...... } }); }
如果异步请求C又需要用到B的结果,又需要一层。如果需要很多层,代码可读性会降低,最后不忍直视了就。所以就有了Promise。有了Promise,代码就是酱紫了:
new Promise(A).done(B);
很清爽很DRY有木有?
ES6已经实现了Promise,不过也可以自己写一个Promise对象:
用两个数组(doneList和failList)来存储成功时的回调函数队列和失败时的回调函数队列。
此对象的方法和属性:
1.state: 当前执行状态,有pending、resolved、rejected3种取值
2.done: 向doneList中添加一个成功回调函数
3.then: 分别向doneList和failList中添加回调函数
4.always: 添加一个无论成功还是失败都会调用的回调函数
5.resolve: 将状态更改为resolved,并触发绑定的所有成功的回调函数
6.reject: 将状态更改为rejected,并触发绑定的所有失败的回调函数
7.when: 参数是多个异步或者延迟函数,返回值是一个Promise兑现,当所有函数都执行成功的时候执行该对象的resolve方法,反之执行该对象的reject方法
具体实现:
var Promise = function() { this.doneList = []; this.failList = []; this.state = 'pending'; }; Promise.prototype = { constructor: 'Promise', resolve: function() { this.state = 'resolved'; var list = this.doneList; for(var i = 0, len = list.length; i < len; i++) { list[0].call(this); list.shift(); } }, reject: function() { this.state = 'rejected'; var list = this.failList; for(var i = 0, len = list.length; i < len; i++){ list[0].call(this); list.shift(); } }, done: function(func) { if(typeof func === 'function') { this.doneList.push(func); } return this; }, fail: function(func) { if(typeof func === 'function') { this.failList.push(func); } return this; }, then: function(doneFn, failFn) { this.done(doneFn).fail(failFn); return this; }, always: function(fn) { this.done(fn).fail(fn); return this; } };
这个不支持链式调用,可以再来个支持链式调用的:
var MPromise = function(func){ this.doneList = []; this.state = 'pending'; func(this.resolve.bind(this)); this.self = this; } MPromise.prototype = { resolve: function(){ var args = arguments[0]; while(true){ args = [args]; this.lastargs = args; if( this.doneList.length == 0 ){ this.state = 'done'; break; } args = this.doneList.shift().apply(this, args); if( args instanceof MPromise ){ this.self = args; args.doneList = args.doneList.concat(this.doneList.slice(0)); this.state = 'done'; this.doneList.length = 0; break; } } }, then: function(callback){ this.doneList.push(callback); if( this.state == 'done' ){ this.state = 'pending'; this.resolve.apply(this, this.lastargs); } return this.self; } }
ES6-Promise
ES6提供了原生Promise对象:从语法上说,Promise是一个对象,从中可以获取异步操作的消息。
基本用法:
ES6中的Promise对象是一个构造函数,用来生成Promise实例。
var promise = new Promise(function(resolve, reject) { // ... some code if (/* 异步操作成功 */){ resolve(value); } else { reject(error); } });
Promise对象接收一个函数作为参数,这个函数的两个参数分别是resolve和reject,这两个函数由Javascript引擎提供,不用自己部署。
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从Pending变为Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then方法分别指定Resolved状态和Reject状态的回调函数。
promise.then(function(value) { // success }, function(error) { // failure });
then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为Resolved时调用,第二个回调函数是Promise对象的状态变为Reject时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。
一个Promise的简单例子:
function timeout(ms) { return new Promise((resolve, reject) => { setTimeout(resolve, ms, 'done'); }); } timeout(100).then((value) => { console.log(value); });
timeout返回了一个Promise实例,延迟一段时间再执行,超过指定时间之后,Promise实例的状态会变成Resolved,会触发then方法绑定的回调函数。
Promise新建后会立即执行:
let promise = new Promise(function(resolve, reject) { console.log('Promise'); resolve(); }); promise.then(function() { console.log('Resolved.'); }); console.log('Hi!'); // Promise // Hi! // Resolved
如上,Promise新建之后立即执行,所以首先输出Promise,然后then方法指定的回调函数,是在当前所有脚本执行之后才执行,所以Resolved最后输出。
一个异步加载图片的例子:
function loadImageAsync(url) { return new Promise(function(resolve, reject) { var image = new Image(); image.onload = function() { resolve(image); }; image.onerror = function() { reject(new Error('Could not load image at ' + url)); }; image.src = url; }); }
这里使用Promise封装了一个图片懒加载,加载成功则调用resolve方法,加载失败会调用rejected方法。
下面是一个用Promise实现Ajax调用的栗子:
var getJSON = function(url) { var promise = new Promise(function(resolve, reject){ var client = new XMLHttpRequest(); client.open("GET", url); client.onreadystatechange = handler; client.responseType = "json"; client.setRequestHeader("Accept", "application/json"); client.send(); function handler() { if (this.readyState !== 4) { return; } if (this.status === 200) { resolve(this.response); } else { reject(new Error(this.statusText)); } }; }); return promise; }; getJSON("/posts.json").then(function(json) { console.log('Contents: ' + json); }, function(error) { console.error('Error', error); });
上面getJSON是对XMLHttpRequest的封装,用来发一个对JSON数据的Http请求,并且返回一个Promise对象。在getJSON内部,resolve和reject函数调用时,都带有参数。
如果调用resolve函数和reject函数时带有参数,那么参数会被传递给回调函数。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可能是另一个Promise实例,表示异步操作的结果有可能是一个值,也有可能是另一个异步操作,比如像下面这样。
var p1 = new Promise(function (resolve, reject) { // ... }); var p2 = new Promise(function (resolve, reject) { // ... resolve(p1); })
上面代码里面的p1和p2都是Promise的实例,但是p2的resolve方法将p1作为参数,即一个异步操作结果是另外一个异步操作。
这里p1的状态就会传到p2,也就是,p1的状态决定了p2的状态。如果p1的状态是Pending,那么p2的回调函数就会等待p1的状态的改变;如果p1的状态是resolved或rejected,那么p2的回调函数会立即执行。
var p1 = new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('fail')), 3000) }) var p2 = new Promise(function (resolve, reject) { setTimeout(() => resolve(p1), 1000) }) p2.then(result => console.log(result)) p2.catch(error => console.log(error)) // Error: fail
上面代码中,p1是一个Promise,3秒之后变为rejected。p2的状态由p1决定,1秒之后,p2调用resolve方法,但是此时p1的状态还没有改变,因此p2的状态也不会变。又过了2秒,p1变为rejected,p2也跟着变为rejected。