Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。ECMAscript 6 原生提供了 Promise 对象。而且ES7中的async/await也是Promise基础实现的。Promise到底有魅力和作用呢?本文将解开它的面纱,探索promise的原理及用法。不用再被一些回调地狱、各种异步调用所头疼烦恼。
什么是Promise?
promise,是承诺的意思。在JavaScript中promise指一个的对象或函数(是一个包含了兼容promise规范then方法的对象或函数)。 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。
- promise的特点
1-1、 Promise的三种状态
- pending: Promise对象实例创建时候的初始状态
- resolved:可以理解为成功的状态
- rejected:可以理解为失败的状态
- 如果是pending状态,则promise:可以转换到resolved或rejected状态。
- 如果是resolved状态,则promise:不能转换成任何其它状态。必须有一个值,且这个值不能被改变。
- 如果是rejected状态,则promise可以:不能转换成任何其它状态。必须有一个原因,且这个值不能被改变。
”值不能被改变”指的是其identity不能被改变,而不是指其成员内容不能被改变。
1-3、promise 有一个 then 方法,就是用来指定Promise 对象的状态改变时确定执行的操作,resolve 时执行第一个函数 (onFulfilled),reject 时执行第二个函数(onRejected)
promiseFn().then(resolve(onFulfilled){ //当promise状态变成fulfilled时,调用此函数 },reject(onRejected){ //当promise状态变成rejected时,调用此函数 });复制代码
- resolve,reject 都是可选参数
- 如果
onFulfilled
不是一个函数,则忽略之。 - 如果
onRejected
不是一个函数,则忽略之。
- 如果
onFulfilled
是一个函数:- 它必须在
promise
fulfilled后调用, 且promise
的value为其第一个参数。 - 它不能在
promise
fulfilled前调用。 - 不能被多次调用。
- 它必须在
- 如果
onRejected
是一个函数,- 它必须在
promise
rejected后调用, 且promise
的reason为其第一个参数。 - 它不能在
promise
rejected前调用。 - 不能被多次调用。
- 它必须在
onFulfilled
和onRejected
只允许在 栈仅包含平台代码时运行.onFulfilled
和onRejected
必须被当做函数调用 (i.e. 即函数体内的this
为undefined
).- 对于一个
promise
,它的then方法可以调用多次.- 当
promise
fulfilled后,所有onFulfilled
都必须按照其注册顺序执行。 - 当
promise
rejected后,所有OnRejected
都必须按照其注册顺序执行。
- 当
then
必须返回一个promise .promise2 = promise1.then(onFulfilled, onRejected);复制代码
- 如果
onFulfilled
或onRejected
返回了值x
, 则执行Promise 解析流程[[Resolve]](promise2, x)
. - 如果
onFulfilled
或onRejected
抛出了异常e
, 则promise2
应当以e
为reason
被拒绝。 - 如果
onFulfilled
不是一个函数且promise1
已经fulfilled,则promise2
必须以promise1
的值fulfilled. - 如果
OnReject
不是一个函数且promise1
已经rejected, 则promise2
必须以相同的reason被拒绝.
- 如果
2、为什么使用promise?有什么好处呢?
- 2-1 对于回调函数: 可以解决回调地狱,如下图: 例如,使用jQuery的ajax多次向后台请求数据时,并且每个请求之间需要相互依赖,则需要回调函数嵌套来解决而形成“回调地狱”。
$.get(url1, data1 => { console.log(data1,"第一次请求"); $.get(data1.url, data2 => { // 第一次请求后的返回url 在此请求后台 console.log(data2,"第二次请求") ..... })})复制代码
这样一来,在处理越多的异步逻辑时,就需要越深的回调嵌套,复制代码
这种编码模式的问题主要有以下几个:
- 代码逻辑书写顺序与执行顺序不一致,不利于阅读与维护。
- 异步操作的顺序变更时,需要大规模的代码重构。
- 回调函数基本都是匿名函数,bug 追踪困难。
- 回调函数是被第三方库代码(如上例中的 ajax )而非自己的业务代码所调用的,造成了 IoC 控制反转。
- 结果不能通过return返回
Promise 怎么解决呢?
let p = url1 => { return new Promise((resolve, reject) => { $.get(url, data => { resolve(data) }); })};// p(url).then(resvloe => { return p(resvloe.url); }).then(resvloe2 => { return resvloe2(resvloe2.url);}).then(resvloe3 => { console.log(resvloe3);}).catch(err => throw new Error(err));当第一个then中返回一个promise,会将返回的promise的结果,传递到下一个then中。这就是比较著名的链式调用了。 复制代码
- 2-2、Promise 也有一些缺点。 首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
3、Promise 使用
- 1. 创建Promise。要想创建一个 promise 对象、可以使用 new 来调用 Promise 的构造器来进行实例化。接收一个excutor执行函数作为参数, excutor有两个函数类型形参resolve reject。
let promise = new Promise(function(resolve, reject) { // 异步处理 // 处理结束后、调用resolve 或 reject});复制代码
- 2.promise中的状态变化。
- 3. promise对象方法 then方法
// onFulfilled 是用来接收promise成功的值 // onRejected 是用来接收promise失败的原因 // then方法是异步的promise.then(onFulfilled, onRejected);复制代码
3-2. resolve(成功): onFulfilled会被调用
let promise = new Promise((resolve, reject) => { resolve('fulfilled'); // 状态由 pending => fulfilled});promise.then(result => { // onFulfilled console.log(result); // 'fulfilled' }, reason => { // onRejected 不会被调用 })复制代码
3-3.reject(失败) onRejected会被调用
let promise = new Promise((resolve, reject) => { reject('rejected'); // 状态由 pending => rejected});promise.then(result => { // onFulfilled 不会被调用 }, reason => { // onRejected console.log(reason); // 'rejected'})复制代码3-4.promise.catch 方法:捕捉错误
(catch 方法是 promise.then(null, rejection) 的别名,用于指定发生错误时的回调函数。)复制代码
promise.catch(onRejected)相当于promise.then(null, onRrejected);// 注意// onRejected 不能捕获当前onFulfilled中的异常promise.then(onFulfilled, onRrejected); // 可以写成:promise.then(onFulfilled) .catch(onRrejected); 复制代码3-5、promise chain 方法每次调用 都返回一个新的promise对象 所以可以链式写法 Promise的静态方法
function step1() { console.log("step1");}function step2() { console.log("step2");}function onRejected(error) { console.log("错误方法:", error);}var promise = Promise.resolve();promise .then(step1) .then(step2) .catch(onRejected) // 捕获前面then方法中的异常复制代码
4、Promise的方法是使用
- Promise.resolve 返回一个fulfilled状态的promise对象
Promise.resolve('成功');// 相当于let promise = new Promise(resolve => { resolve('成功');});复制代码
//库中实现Promise.resolve = function (val) { return new Promise((resolve, reject) => resolve(val))}复制代码
2.Promise.reject 返回一个rejected状态的promise对象
var p = Promise.reject('出错了'); p.then(null, function (s){ console.log(s)});// 出错了复制代码
3.Promise.all 接收一个promise对象数组为参数
只有全部为resolve才会调用 通常会用来处理 多个并行异步操作
const p1 = new Promise((resolve, reject) => { resolve(1);});const p2 = new Promise((resolve, reject) => { resolve(2);});const p3 = new Promise((resolve, reject) => { reject(3);});Promise.all([p1, p2, p3]).then(data => { console.log(data); // [1, 2, 3] 结果顺序和promise实例数组顺序是一致的}, err => { console.log(err);});复制代码
//库中实现Promise.all = function(arr) { return new Promise((resolve, reject) => { let num = 0,innerArr = []; function done(index,data){ innerArr[index] = data; num ++; if(num === arr.length){ resolve(innerArr); } } for(let i =0 ;i{ done(i,res); },reject); // 有一个失败 就返回 } })}复制代码
4.Promise.race 接收一个promise对象数组为参数
Promise.race 只要有一个promise对象进入 FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理。
function timerPromisefy(delay) { return new Promise(function (resolve, reject) { setTimeout(function () { resolve(delay); }, delay); });}var startDate = Date.now();Promise.race([ timerPromisefy(10), timerPromisefy(20), timerPromisefy(30)]).then(function (values) { console.log(values); // 10});复制代码
// 库中实现Promise.race = function(arr) { return new Promise((resolve, reject) => { arr.forEach((item, index) => { item.then(resolve, reject); }); });}复制代码
5.Promise的finally
Promise.finally = function (callback) { let P = this.constructor; return this.then( value => P.resolve(callback()).then(() => value), reason => P.resolve(callback()).then(() => { throw reason }) );};复制代码
5、prmoise 代码库实现
/** * Promise 实现 遵循promise/A+规范 * Promise/A+规范译文: * https://promisesaplus.com/ */// 判断x是不是promise 根据规范function resolvePromise(promise2, x, resolve, reject) { // 1、如果promise2 和 x 指向相同的值, 使用 TypeError做为原因将promise拒绝。 (就会导致循环引用报错) if (promise2 === x) { return reject(new TypeError('循环引用')); } // 避免多次调用 let isUsed = false; // 2、如果x是一个promise对象 (该判断和下面 判断是不是thenable(thenable 是一个包含了then方法的对象或函数)对象重复 所以可有可无) /** * 如果x是pending状态,promise必须保持pending走到x resolved或rejected. 如果x是resolved状态,将x的值用于resolve promise. 如果x是rejected状态, 将x的原因用于reject promise.. */ // if (x instanceof Promise) { // 获得它的终值 继续resolve // if (x.status === 'pending') { // 如果为等待态需等待直至 x 被执行或拒绝 并解析y值 递归 // x.then(y => { // resolvePromise(promise2, y, resolve, reject); // }, reason => { // reject(reason); // }); // } else { // 如果 x 已经处于执行态/拒绝态(值已经被解析为普通值),用相同的值执行传递下去 promise // x.then(resolve, reject); // } // // 3、如果 x 为对象或者函数 // /** // * 1.将 then 赋为 x.then. // 2.如果在取x.then值时抛出了异常,则以这个异常做为原因将promise拒绝。 // 3.如果 then 是一个函数, 以x为this调用then函数, 且第一个参数是resolve,第二个参数是reject,且: // 3-1.当 resolve 被以 y为参数调用, 执行 [[Resolve]](promise, y). // 3-2.当 reject 被以 reason 为参数调用, 则以reason为原因将promise拒绝。 // 3-3.如果 resolvee 和 reject 都被调用了,或者被调用了多次,则只第一次有效,后面的忽略。 // 3-4.如果在调用then时抛出了异常,则: // 如果 resolve 或 reject 已经被调用了,则忽略它。isUsed = true; // 否则, 以e为reason将 promise 拒绝。 // 4.如果 then不是一个函数,则 以x为值resolve promise。 // */ // } else if (x !== null && ((typeof x === 'object') || (typeof x === 'function'))) { try { // 是否是thenable对象(具有then方法的对象/函数) 如果在取x.then值时抛出了异常,则以这个异常做为原因将promise拒绝。 let then = x.then; if (typeof then === 'function') { then.call(x, y => { // 如果y是promise就继续递归解析promise if (isUsed) return; isUsed = true; resolvePromise(promise2, y, resolve, reject); }, reason => { // 只要失败了就失败了 不用再递归解析是都是promise if (isUsed) return; isUsed = true; reject(reason); }) } else { // 说明是一个函数,则 以x为值resolve promise。 resolve(x); } } catch (e) { if (isUsed) return; isUsed = true; reject(e); } } else { //4、如果 x 不是对象也不是函数,则以x为值 resolve promise。例如 x = 123 或 x ='成功' resolve(x); }}// Promise 对象代表了未来将要发生的事件,用来传递异步操作的消息。// Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。class Promise { // Promise 是一个类, new Promise 返回一个 promise对象 接收一个ex执行函数作为参数, ex有两个函数类型形参resolve reject /** * var promise = new Promise(function(resolve, reject) { //会立即执行 // 异步处理 // 处理结束后、调用resolve 或 reject //当异步代码执行成功时,我们才会调用resolve(...), 当异步代码失败时就会调用reject(...) //在本例中,我们使用setTimeout(...)来模拟异步代码,实际编码时可能是XHR请求或是HTML5的一些API方法. setTimeout(function(){ resolve("成功!"); //代码正常执行! }, 250); }); promise.then(function(successMessage){ //successMessage的值是上面调用resolve(...)方法传入的值. //successMessage参数不一定非要是字符串类型,这里只是举个例子 console.log("Yay! " + successMessage); }); */ constructor(ex) { // this.status = 'pending'; // 初始状态 (表示 未开始) this.resolveVal = undefined; // resolved状态时(表示成功) 返回的信息 this.rejectVal = undefined; // rejected状态时(表示失败) 返回的信息 this.onResolveCallBackFns = []; // 存储resolved状态对应的onResolved函数 (因为可以链式调用可以多个then方法) this.onRejectCallBackFns = []; // 存储rejected状态对应的onRejected函数 /** * 一个Promise必须处在其中之一的状态:pending, fulfilled 或 rejected. * * 如果是pending状态,则promise: 可以转换到resolved或rejected状态。 如果是resolved状态,则promise: 不能转换成任何其它状态。 必须有一个值,且这个值不能被改变。 如果是rejected状态,则promise可以: 不能转换成任何其它状态。 必须有一个原因,且这个值不能被改变。 ”值不能被改变”指的是其identity不能被改变,而不是指其成员内容不能被改变。 */ let resolve = (data) => { // data 成功态时接收的终值 if (this.status === 'pending') { // 只能由pedning状态 => resolved状态 避免调用多次resolve reject) this.status = 'resolved'; this.resolveVal = data; this.onResolveCallBackFns.forEach(cb => cb()); } } let reject = (err) => { if (this.status === 'pending') { // 只能由pedning状态 => rejected状态 避免调用多次resolve reject) this.status = 'rejected'; this.rejectVal = err; this.onRejectCallBackFns.forEach(cb => cb()); } } // 捕获在ex执行器中抛出的异常 // new Promise((resolve, reject) => { // throw new Error('error in ex') // }) try { ex(resolve, reject); } catch (e) { reject(e); } } // 按照prmoise a+ 规范 then方法接受两个参数 then方法是异步执行的 必须返回一个promise then(resolve, reject) { // then 方法 resolve,reject 都是可选参数 保证参数后续能够继续执行 // 1.1、 如果resolve不是一个函数,则忽略之。 如果reject不是一个函数,则忽略之。 resolve = typeof resolve == 'function' ? resolve : y => y; reject = typeof reject == 'function' ? reject : err => { throw err }; let promise2; // then必须返回一个promise if (this.status === 'pending') { // 等待态 // 当异步调用resolve/rejected时 将resolve/reject收集暂存到集合中 promise2 = new Promise((res, rej) => { this.onResolveCallBackFns.push(() => { setTimeout(() => { try { // resolvePromise可以解析x和promise2之间的关系 /** 如果resolve 或 reject 返回了值x, 则执行Promise 解析流程[[Resolve]](promise2, x). // 如果resolve 或 reject 抛出了异常e, 则promise2应当以e为rejectVal被拒绝。 // 如果 resolve 不是一个函数且promise1已经resolved,则promise2必须以promise1的值resolved. // 如果 reject 不是一个函数且promise1已经rejected, 则promise2必须以相同的rejectVal被拒绝. */ let x = resolve(this.resolveVal); resolvePromise(promise2, x, res, rej); } catch (e) { rej(e); } }, 0); }); this.onRejectCallBackFns.push(() => { setTimeout(() => { try { let x = reject(this.rejectVal); resolvePromise(promise2, x, res, rej); } catch (e) { rej(e); } }, 0); }); }); } if (this.status == 'resolved') { // 它必须在promise resolved后调用, 且promise的value为其第一个参数。 promise2 = new Promise((res, rej) => { // 用setTimeout方法原因 : // 1、方法是异步的 // 2、 对于一个promise,它的then方法可以调用多次.(当在其他程序中多次调用同一个promise的then时 由于之前状态已经为resolved/rejected状态,则会走的下面逻辑), // 所以要确保为resolved/rejected状态后 也要异步执行resolve/reject 保持统一 setTimeout(() => { try { let x = resolve(this.resolveVal); resolvePromise(promise2, x, res, rej); // // resolvePromise可以解析x和promise2之间的关系 } catch (e) { rej(e); } }) }) } if (this.status == 'rejected') { // 必须在promise rejected后调用, 且promise的rejectVal为其第一个参数 promise2 = new Promise((res, rej) => { // 方法是异步的 所以用setTimeout方法 setTimeout(() => { try { let x = reject(this.rejectVal); resolvePromise(promise2, x, res, rej); // resolvePromise可以解析x和promise2之间的关系 } catch (e) { rej(e); } }); }) } return promise2; // 调用then后返回一个新的promise } // catch接收的参数 只用错误 catch就是then的没有成功的简写 catch(err) { return this.then(null, err); }}/** * Promise.all Promise进行并行处理 * 参数: arr对象组成的数组作为参数 * 返回值: 返回一个Promise实例 * 当这个数组里的所有promise对象全部变为resolve状态的时候,才会resolve。 */Promise.all = function (arr) { return new Promise((resolve, reject) => { let num = 0, innerArr = []; function done(index, data) { innerArr[index] = data; num++; if (num === arr.length) { resolve(innerArr); } } for (let i = 0; i < arr.length; i++) { arr[i].then((res) => { done(i, res); }, reject); // 有一个失败 就返回 } })}/** * Promise.race * 参数: 接收 promise对象组成的数组作为参数 * 返回值: 返回一个Promise实例 * 只要有一个promise对象进入 resolved 或者 rejected 状态的话,就会继续进行后面的处理(取决于哪一个更快) */Promise.race = function (arr) { return new Promise((resolve, reject) => { arr.forEach((item, index) => { item.then(resolve, reject); }); });}// Promise.reject 返回一个rejected状态的promise对象Promise.resolve = function (val) { return new Promise((resolve, reject) => resolve(val))}// .Promise.resolve 返回一个fulfilled状态的promise对象Promise.reject = function (val) { return new Promise((resolve, reject) => reject(val));}Promise.deferred = Promise.defer = function () { let dfd = {}; dfd.promise = new Promise((resolve, reject) => { dfd.resolve = resolve; dfd.reject = reject; }) return dfd;}module.exports = Promise;复制代码
6、测试
let p = new Promise((resolve, reject) => { reject('err');})p.then().then().catch(r => { console.log(r);}).then(data => { console.log('data', data);})执行结果:errdata undefined复制代码
let fs = require('fs');function read() { // 好处就是解决嵌套问题 // 坏处错误处理不方便了 let defer = Promise.defer(); fs.readFile('./1.txt', 'utf8', (err, data) => { if (err) defer.reject(err); defer.resolve(data) }); return defer.promise;}read().then(data => { console.log(data);});执行结果我是1.txt内容复制代码