学习自阮一峰老师 ECMAScript 6 入门 Promise介绍参考 promise-实现 Promise属于微任务,而模拟的 Promise 都是使用setTimeout,属于宏任务。所以在某些情况下会有bug,需要注意,如:setTimeout(‘console.log(1)’)new Promise(resolve=>{ console.log(2) resolve()}).then(=>{ console.log(3)})正确的结果应该是 2 3 1,但是模拟的 Promise 返回 1 2 3。bluebird,es6-promise等也有这个问题。代码实现// 三种状态 + 1var PENDING = “pending”;var RESOLVED = “resolved”;var REJECTED = “rejected”;var FINALLY = “finally”;var Promise = window.Promise || function(){ var Promise = function(fn){ var that = this; that.currentState = PENDING; that.value = undefined; // 用于保存 then 中的回调,只有当 promise // 状态为 pending 时才会缓存,并且每个实例至多缓存一个 that.Callbacks = [] //这个函数是封装的resolve,reject的公共方法,这个方法不执行,then、catch、finally中的回都不执行 var stateF = function(state, value){ setTimeout(function(){ // 异步执行,保证执行顺序 if (that.currentState === PENDING) { that.value = value; var cb; //回调状态,默认为Promise状态 //通过修改回调状态,来确定运行哪个回调 //如没有抛出错误,则不运行 REJECTED 回调,抛出错误,则catch之前的 RESOLVED 回调不运行 var cbState = that.currentState = state; while(cb = that.Callbacks.shift()){ //状态为 RESOLVED,运行then回调,状态为 REJECTED,运行catch回调 if(cb.state == cbState || cb.state == FINALLY){ try{ if(cb.state == FINALLY){ //状态为 finally 的回调不接受参数,不返回参数 cb.fn() }else{ //运行正确,回调状态变为 RESOLVED cbState = RESOLVED //该回调的返回值是下一个 then 回调的参数 that.value = cb.fn(that.value) } }catch(err){ //运行错误,回调状态变为 REJECTED cbState = REJECTED //该错误是下一个 catch 回调的参数 that.value = err } } } } }) } var resolve = function(value){ if (value instanceof Promise) { // 如果 value 是个 Promise,递归执行 //等 Promise 状态返回后,重新运行 resolve 或 reject return value.then(resolve, reject) } stateF(RESOLVED, value) } var reject = function (reason) { stateF(REJECTED, reason) } // new Promise(() => throw Error(’error)) try { fn(resolve, reject); } catch (e) { reject(e); } } /* Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。 const p = Promise.all([p1, p2, p3]); 上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。 Promise.all方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。 * / Promise.all = function(arr){ arr = Array.prototype.slice.apply(arr) var values = [], count = 0; var p = new Promise(function(resolve, reject){ arr.forEach(function(item, index){ Promise.resolve(item).then(function(value){ values[ index ] = value; count++ if(p.currentState === PENDING && count === arr.length){ resolve(values) } }).catch(function(e){ p.currentState === PENDING && reject(e) }) }) }) return p } / Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。 const p = Promise.race([p1, p2, p3]); 只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。 * / Promise.race = function(arr){ arr = Array.prototype.slice.apply(arr) var p = new Promise(function(resolve, reject){ arr.forEach(function(item, index){ Promise.resolve(item).then(function(value){ p.currentState === PENDING && resolve(value) }).catch(function(e){ p.currentState === PENDING && reject(e) }) }) }) return p } / 将现有对象转为 Promise 对象 如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。 参数是一个具有then方法的对象,Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。 如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。 Promise.resolve方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。 * / Promise.resolve = function(value){ if (value instanceof Promise) { return value } var fn = value.toString() === “[object Object]” && typeof value.then === ‘function’ ? value.then : function(resolve){ resolve(value) }; return new Promise(fn) } / Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。 Promise.reject()方法的参数,会原封不动地作为reject的参数,变成后续方法的参数。这一点与Promise.resolve方法不一致 * / Promise.reject = function(value){ return new Promise(function(reject){ reject(value) }) } //提取的公共方法,then,catch,finally共用 function bindF(state, fn){ if(typeof fn === ‘function’){ if(this.currentState === PENDING){ this.Callbacks.push({ state: state, fn: fn }) }else{ //状态改变后,不是REJECTED,直接运行 state !== REJECTED && fn(this.value) } } return this } / then方法的作用是为 Promise 实例添加状态改变时的回调函数。 then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。 * / Promise.prototype.then = function(resolve, reject){ bindF.call(this, RESOLVED, resolve) return bindF.call(this, REJECTED, reject) } / Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名 用于指定发生错误时的回调函数。 * / Promise.prototype.catch = function(reject){ return bindF.call(this, REJECTED, reject) } / finally方法用于指定不管 Promise 对象状态是 resolve 或 reject ,都会执行的操作。 * */ Promise.prototype.finally = function(fina){ return bindF.call(this, FINALLY, fina) } return Promise}()if (typeof Promise.try !== ‘function’) { Promise.try = function(fn){ return new Promise(function (resolve) { resolve(fn()); }); }}最后的 Promise.try实际开发中,经常遇到一种情况:不知道或者不想区分,函数f是同步函数还是异步操作,但是想用 Promise 来处理它。 一般的,为了确保同步函数同步执行,异步函数异步执行,我们会这么做: const f1 = () => new Promise(resolve=>{ setTimeout(=>{ resolve([1,2,3]) },1000) }); const f2 = _ => ({a: 1}); new Promise( resolve => resolve(f1()) ).then(data=>{console.log(data)}) new Promise( resolve => resolve(f2()) ).then(data=>{console.log(data)}) //{a: 1} //[1, 2, 3]同时,这么写还可以更好的处理错误。如果同步或者异步方法本身是错误的,我们需在外层套一层try来处理,这种写法则可以省略try,使代码更美观。正对这种写法,现在已经有提案,提供Promise.try方法替代上面的写法。