Javascript的特点是异步,Javascript不能等待,如果你实现某件需要等待的事情,你不能停在那里一直等待结果回来,相反,底线是使用回调callback:你定义一个函数,这个函数只有等到结果可用时才能被调用。
这种回调模型对于好的代码组织是没有问题的,但是也可以通过从原始回调切换到promise解决很多问题,将promise看成是一个标准的数据容器,这样会简化你的代码组织,可以成为基于promise的架构。
1.为什么要使用promise
0 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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
/* 需求:异步的按顺序去读取1.txt、2.txt、3.txt文件的内容 假设 1.txt内容为111 、2.txt内容为222、3.txt内容为333 */ var fs = require('fs');、 fs.readFile('./files/1.txt','utf8',function(err,data){ if(err){ throw err; } console.log(data); }) fs.readFile('./files/1.txt','utf8',function(err,data){ if(err){ throw err; } console.log(data); }) fs.readFile('./files/1.txt','utf8',function(err,data){ if(err){ throw err; } console.log(data); }) /* 结果:111 222 333。(好像满足需求啊) */ /* 问题:上面的代码虽然正常按照顺序读取了文件,因为文件的内容都非常少,io读取耗时少,但当2.txt内容较多时候,2.txt的内容是最后输出的。因为他们都是异步操作,不知道谁先读取完。 这得取决于异步任务IO的耗时。 */ /* 常规解决办法: 在第一个异步任务读取成功之后再读取第二个异步任务, 第二成功后,在读取第三个异步任务 */ //读取第一个异步任务 fs.readFile('./files/1.txt','utf8',function(err,data){ if(err){ throw err; } console.log(data); //读取第二个异步任务 fs.readFile('./files/2.txt','utf8',function(err,data){ console.log(data); //读取第三个异步任务 fs.readFile('./files/3.txt','utf8',function(err,data){ console.log(data); }) }) }) /* 结果: 111 222 333 (这必须按照顺序输出的,结果杠杆的) */ /* 问题: 以上按照顺序执行多个异步任务产生的问题:`回调地狱`问题(层层包裹进行回调,代码也不够优雅) */ /* 解决办法:采用es6,提供的promise来解决上述产生的问题。 */ |
2.Promise基本使用
promise介绍:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
//简单说promise就是用来执行异步任务的,可以解决上面所说的回调地狱问题, //语法:new Promise(function(callback){}) var fs = require('fs'); var promise = new Promise(function(resolve,reject){ //这里就是写异步的代码,只要new Promise操作,就会立刻执行这里的代码 //两个参数 resolve 异步执行成功的回调函数,reject异步执行失败的回调函数 fs.readFile('./files/1.txt', 'utf8', function (err, data) { if (err) { throw err; } console.log(data); }) }); //运行结果:111 |
3.promise参数resolve和reject
两个参数
- resolve 异步执行成功的回调函数,
- reject异步执行失败的回调函数
0 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 |
var fs = require('fs'); var promise = new Promise(function (resolve, reject) { //两个参数: resolve 成功的回调函数名 , reject失败的回调函数名 fs.readFile('./files/111.txt', 'utf8', function (err, data) { if (err) { //说明失败了,要执行失败的回调函数 reject(err); } else { //成功的逻辑 resolve(data); } //等价于 // err ? reject(err) : resolve(data); }) }); //new Promise返回的是一个promise对象, //这个对象有一个方法叫做then,在其原型对象上 //通过这then方法可以指定成功和失败的回调函数 //语法:promise.then(successCallback,errorCallback); promise.then(function (data) { //then第一个函数是成功的回调,参数是resolve(data)中的data console.log('成功:' + data); // 若成功,运行结果:成功:111 }, function (err) { //then第二个函数是失败的回调函数,参数是reject(err)中的err错误对象 console.log('失败:' + err); }); |
4.解决回调地狱问题
0 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 32 33 34 |
//封装一个异步读取文件的内容的函数 //此函数返回对应异步任务的promise对象 function getFileByPath(path) { return new Promise(function (resolve,reject) { fs.readFile(path, 'utf8', function (err, data) { if(err){ reject(err); //失败 }else{ resolve(data); //成功 } }) }); } //由于then通过getFileByPath返回的是一个promise对象,所以可以继续.then串联调用(链式调用) getFileByPath('./files/1.txt') .then(function(data){ console.log("成功:"+data); return getFileByPath('./files/2.txt'); },function(err){ console.log("失败:"+err); return getFileByPath('./files/2.txt'); }) .then(function(data){ console.log("成功:"+data); return getFileByPath('./files/3.txt'); },function(err){ console.log("失败:"+err); return getFileByPath('./files/3.txt'); }) .then(function(data){ console.log("成功:"+data); },function(err){ console.log("失败:"+err); }); |
注意:上面一定会保证按照.then的顺序去执行异步的代码,
如果某个异步任务有错误则会触发对应then的第二个错误的回调函数。即每个promise对象都有对应的错误回调,对其他的promise不影响。毕竟promise的英文翻译就是保证。
5.catch的使用
catch也是在Promise.prototype原型上定义的。
需求:如果多个promise任务,其中有一个失败了,则终止后面的所有的promise执行
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
getFileByPath('./files/1.txt') .then(function(data){ console.log("成功:"+data); return getFileByPath('./files/2.txt'); }) //上面的then通过getFileByPath返回的是一个promise对象,所以可以继续.then串联调用(链式调用) .then(function(data){ console.log("成功:"+data); return getFileByPath('./files/3.txt'); }) .then(function(data){ console.log("成功:"+data); }) .catch(function(err){ // catch作用: 上面所有的promise如果其中一个有错误, //则终止下面所有的promise执行,且直接进入到catch中获取对应promise的异常错误信息 console.log('catch:'+err); }) |
注意:如果在then中定义了错误回调则不会进入到上面的catch中,这是因为promise对象指定了对应的错误处理回调。
6 then-catch-finally
0 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 |
var fs = require('fs'); var promise = new Promise(function (resolve, reject) { //两个参数: resolve 成功的回调函数名 , reject失败的回调函数名 fs.readFile('./files/11.txt', 'utf8', function (err, data) { if (err) { //说明失败了,要执行失败的回调函数 reject(err); } else { //成功的逻辑 resolve(data); } }) }); //new Promise返回的是一个promise对象, //这个对象有一个方法叫做then,在其原型对象上 //通过这then方法可以指定成功和失败的回调函数 //语法:promise.then(successCallback,errorCallback); promise.then(function (data) { //then第一个函数是成功的回调,参数是resolve(err)中的data console.log('成功:' + data); // 若成功,运行结果:成功:111 }).catch(function(err){ //then第二参数错误回调换成这里catch也行,两者选其一 console.log('err'); }).finally(function(){ //无论失败成功都会执行 console.log('完成'); }) |
7. Promise.all静态方法的使用
参照:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise#%E6%96%B9%E6%B3%95
需求:把所有的promise执行成功的结果保存在一个数组中。。
0 1 2 3 4 5 6 7 8 9 10 |
var promise1 = getFileByPath('./files/1.txt'); var promise2 = getFileByPath('./files/2.txt'); var promise3 = getFileByPath('./files/3.txt'); //执行多个异步任务, Promise.all([promise3,promise1,promise2]).then(function(data){ console.log(data); },function(err){ console.log('错误了:'+err); }) //Promise.all尤其是在一个页面上发起多个ajax请求的时候,如果要同时保证他们成功,则使用Promise.all是最合适不过的了。其中一个失败则也可以在then的第二个回调做失败的逻辑。 |
若都成功,运行结果:['333','111','22']
注意:Promise.all的成功结果是返回一个数组,且数组中数据的结果顺序与Promise.all数组的传参顺序是一样的。
原文:https://www.jianshu.com/p/c2ff916fadc5