网上关于Promise的文章非常之多,但我觉得Promise的难点不在语法上,而是在链式调用和错误处理部分。本文将主要就这两部分进行展开说明。
1. Promise的典型应用
Promise的提出,解决了异步函数回调地狱的问题,也更加符合开放封闭原则;
示例一:
原始写法:
1 2 3 4 5 6 7
| image.onload = function() { cb_success() };
function cb_success(){ console.log('success'); }
|
Promise形式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| function loadImg(src){ let pr = new Promise((resolve,reject)=>{ var img = document.createElement('img'); img.onload = function(){ resolve(img) } img.onerror = function(err){ reject('加载失败') } img.src = src; } )
return pr; }
let pr = loadImg('https://www.baidu.com/img/superlogo_c4d7df0a003d3db9b65e9ef0fe6da1ec.png?where=super');
pr.then((img)=>{ console.log(img.width) return img }).then((img)=>{ console.log(img.height) }).catch(err=>{ console.log(err) })
|
示例二:
1 2 3 4 5
| fs.readFile(fileA, 'utf-8', function (err, data) { fs.readFile(fileB, 'utf-8', function (err, data) { }); });
|
Promise形式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const fs = require('fs');
new Promise((resolve,reject)=>{ fs.readFile('./index.html',(err,data)=>{ resolve(data) }) }).then(res=>{ console.log(res)
return new Promise((resolve,reject)=>{ fs.readFile('./page.html',(err,data)=>{ resolve(data) }) }) }).then(res=>{ console.log(res) })
|
2. Promise异常捕获
@1. 不止在reject的时候会进入catch。前面语法有报错,同样会自动进入catch进行捕获。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const fs = require('fs');
new Promise((resolve,reject)=>{ f11s.readFile('./index.html',(err,data)=>{ if(err){ reject(err) return; } resolve(data) }) }).then(res=>{ console.log(res) }).catch(err=>{ console.log(err) })
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const fs = require('fs');
new Promise((resolve,reject)=>{ fs.readFile('./in123dex.html',(err,data)=>{ if(err){ throw new Error(err); return; } resolve(data) }) }).then(res=>{ console.log(res) }).catch(err=>{ console.log(err) })
|
@2 then里面写两个回调的写法在链式调用时,catch捕获异常容易出问题;所以一般在链式调用时推荐在最后写一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| let pr = new Promise(function(resolve,reject){ reject(1) })
pr.then( ()=>console.log('success 1'), ()=>console.log('error 1') ) pr.then( ()=>console.log('success 2'), ()=>console.log('error 2') )
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| let pr = new Promise(function(resolve,reject){ reject(1) })
pr.then( ()=>console.log('success 1'), ()=>console.log('error 1') ).then( ()=>console.log('success 2'), ()=>console.log('error 2') )
|
所以推荐写法:统一进行错误捕捉
1 2 3 4 5 6 7 8 9 10 11 12 13
|
let pr1 = new Promise(function(resolve,reject){ reject(1) })
pr1.then( ()=>console.log('success 1') ).then( ()=>console.log('success 2') ).catch(err=>{ console.log(err); })
|
3. Promise链式调用
then函数必须返回Promise的实例。
首先,下面这段代码是可以执行的,因为then方法默认返回一个Promise,Promise就有then方法。想要传递参数需要return;
1 2 3 4 5 6 7 8
| new Promise((resolve, reject) => { resolve('li') }).then((value) => { console.log(value) return 'hello ' + value; }).then(val=>{ console.log(val) })
|
但Promise规范规定 then回调函数return的应该是一个Promise
,所以可以这么写
1 2 3 4 5 6 7 8 9 10
| new Promise((resolve, reject) => { resolve('li') }).then((value) => { console.log(value) return new Promise(resolve=>{ resolve('hello ' + value); }) }).then(val=>{ console.log(val) })
|
正常使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const fs = require('fs');
new Promise((resolve,reject)=>{ fs.readFile('./index.html',(err,data)=>{ resolve(data) }) }).then(res=>{ console.log(res)
return new Promise((resolve,reject)=>{ fs.readFile('./page.html',(err,data)=>{ resolve(data) }) }) }).then(res=>{ console.log(res) })
|
4. Promise.all**
Promise.all的返回值默认是一个数组。在参数里的全部promise运行完成后执行。
如下是工作中的一个需求,需要先从前面两个接口中获取通行证id,再根据这两个id从第三个接口中取聊天记录;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| const p1 = getData({ url: 'url1', method: 'get', params: { passportid: info.agentPassportID } }).then(res => res) .catch(e => alert('获取agentID失败'));
const p2 = getData({ url: 'url2', method: 'get', params: { passportid: info.customerPassportID } }).then(res => res) .catch(e => alert('获取customerID失败'));
Promise.all([p1, p2]) .then(result => { return getData({ url: 'url3', method: 'get', params: { command: 'getMessage', from: result[0].data.Data, sendto: result[1].data.Data } }) }) .catch(e => alert('获取聊天记录失败'));
|
5. Promise.race**
与Promise.all相同,区别是只要有一个完成就算完成。
常见用法:
- 把异步操作和定时器放在一起
- 如果定时器先触发,就认定超时,告知用户
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| let p1 = new Promise((resolve,reject)=>{ console.log('this is p1') setTimeout(function(){ resolve('aaa') },5000) }) let p2 = new Promise((resolve,reject)=>{ console.log('this is p2') setTimeout(function(){ resolve('bbb') },2000) })
Promise.race([p1,p2]).then(val=>{ console.log(val) })
|
6. 在IE中使用Promise**
IE8,9,10,11均不原生支持Promise;IE Edge支持
解决方案: BlueBird/Promise Polyfill