“承诺将来会执行”的对象:Promise对象。本质上是一个绑定了回调的对象,而不是将回调传进函数内部。
Promise最直接的好处就是链式调用。
Promise是一个构造函数,使用new操作符新建一个对象
var p = new Promise(function(resolve,reject){ setTimeout(function(){ console.log('异步1'); var num = Math.random()*10; if(num<5) { resolve(num); }else { reject('fail'); } },1000); });
Promise的构造函数接受一个参数,是函数,并且传入连个参数:resolve,reject,分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数。准确的讲,resolve是将Promise的状态置为fulfilled,reject是将Promise的状态置为rejected。
在上面的代码中,我们执行了一个异步操作,setTimeout,1秒后输出"异步1",并且调用resolve/reject方法。
注意:我只是new了一个对象,并没有调用它,但我们穿进去的函数就已经执行了,所以我们用Promise的时候一般是包含在一个函数中,在需要的时候去运行这个函数:
function async() { var p=new Promise(function (resolve,reject) { setTimeout(function () { console.log('1执行完成'); var num = Math.random()*10; if(num<5) { resolve(num); }else { reject('fail'); } },1000); }); return p;
}
执行以上函数我们得到了一个Promise对象。还记得Promise对象上有then,catch方法吧,看下面的代码:
async().then(function(data){ console.log(data); //......... })
在async()的返回上直接调用then方法,then接受一个参数,并且会拿到我们在async中调用resolve时传的参数。
链式操作的用法
Promise不只是能够简化层层回调的写法,而实质上,Promise的精髓是“状态”,通过维护状态、传递状态的方式来使得回调函数能够及时调用。
所以使用Promise的正确场景是这样的:
async().then(function (data) { setTimeout(function () { console.log(data); },1000) return async2(); }).then(function (data) { setTimeout(function () { console.log(data); },1000) return async3(); }).then(function (data) { setTimeout(function () { console.log(data); },1000) }).catch(function (reason) { console.log(reason) })
其中async2和async3与async相似。
then方法也可以接收两个参数,第一个参数对应resolve的回调,第二个参数对应reject的回调。
function getNumber(){ var p = new Promise(function(resolve, reject){ //做一些异步操作 setTimeout(function(){ var num = Math.ceil(Math.random()*10); //生成1-10的随机数 if(num<=5){ resolve(num); } else{ reject('数字太大了'); } }, 2000); }); return p; } getNumber() .then( function(data){//resolve的回调函数 console.log('resolved'); console.log(data); }, function(reason, data){//reject的回调函数 console.log('rejected'); console.log(reason); } );
catch的用法
Promise对象除了then方法,还有一个catch方法,和then的第二个参数一样,用来指定reject的回调。
catch还有一个作用:在执行resolve的回调时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进入到这个catch方法中。与try-catch语句有相同的功能。
all的用法
Promise的all方法提供了并行异步操作的能力,并且在所有异步操作执行完后才执行回调。
Promise.all([async(),async2(),async3()]).then(function (data) { console.log(data) }).catch(function (reason) { console.log('reason:'+reason) })
用Promise.all来执行,all接收一个数组参数,里面的值最终都返回Promise对象。这样,三个异步操作的并行执行,等到它们都执行完后才会进入到then里面。那么,三个异步操作返回的数据的结果都放入发哦一个数组中传给then。
有了all,你就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据,是不是很酷?有一个场景是很适合用这个的,一些游戏类的素材比较多的应用,打开网页时,预先加载需要用到的各种资源如图片、flash以及各种静态文件。所有的都加载完后,我们再进行页面的初始化。
race的用法
race也接收一个数组参数,表示只要有一个异步完成,就调用then方法。
要注意的是,当then里面的回调函数开始执行的时候,其他异步函数并没有停止,仍然在执行。
race的使用场景:给某个异步请求设置超时时间,并且在超时后执行相应的操作,代码如下:
//请求某个图片资源 function requestImg(){ var p = new Promise(function(resolve, reject){ var img = new Image(); img.onload = function(){ resolve(img); } img.src = 'xxxxxx'; }); return p; } //延时函数,用于给请求计时 function timeout(){ var p = new Promise(function(resolve, reject){ setTimeout(function(){ reject('图片请求超时'); }, 5000); }); return p; } Promise .race([requestImg(), timeout()]) .then(function(results){ console.log(results); }) .catch(function(reason){ console.log(reason); });
es6写法
function timeout(ms) { return new Promise((resolve, reject) => { setTimeout(resolve, ms, 'done'); }); } timeout(100).then((value) => { console.log(value); });
用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 (err) { console.error('出错了',err); })
面试遇到一个问题:用promise实现node里面的promisify
首先面试官解释了promisify的使用:
const fs = require('fs'); fs.readFile('/foo/bar','utf8',function(err,content){ }); const readFile = promisify(fs.readFile); readFile('/foo/bar','utf8').then(content=>{ console.log(content); },err=>{ console.log(err); });
首先promisify的参数是一个函数,返回值也是一个可以接收参数的函数。
function promisify(fn){ return function(...args){ return new Promise((resolve,reject)=>{ fn(...args,function(err,value){ if(err){ return reject(err); } return resolve(value); }) }) } }