JavaScript异步与Promise实现
MosDieter
7年前
<p>在阅读本文之前,你应该已经了解JavaScript异步实现的几种方式:回调函数,发布订阅模式,Promise,生成器(Generator),其实还有async/await方式,这个后续有机会会介绍。本篇将介绍Promise,读完你应该了解什么是Promise,为什么使用Promise,而不是回调函数,Promise怎么使用,使用Promise需要注意什么,以及Promise的简单实现。</p> <h2>前言</h2> <p>如果你已经对JavaScript异步有一定了解,或者已经阅读过本系列的其他两篇文章,那请继续阅读下一小节,若你还有疑惑或者想了解JavaScript异步机制与编程,可以阅读一遍这两篇文章:</p> <ul> <li><a href="/misc/goto?guid=4959748574724092013" rel="nofollow,noindex">JavaScript之异步编程简述</a></li> <li><a href="/misc/goto?guid=4959748574831239029" rel="nofollow,noindex">JavaScript之异步编程</a></li> </ul> <h2>回调函数</h2> <p>回调函数,作为JavaScript异步编程的基本单元,非常常见,你肯定对下面这类代码一点都不陌生:</p> <pre> <code class="language-javascript"> component.do('purchase', funcA); function funcA(args, callback) { //... setTimeout(function() { $.ajax(url, function(res) { if (res) { callback(res) } else {//...} }); }, 300); funcB(); setTimeout(function() { $.ajax(arg, function(res) { if (res) { callback(res); } }); }, 400); }</code></pre> <p>上面这些代码,一层一层,嵌套在一起,这种代码通常称为回调地狱,无论是可读性,还是代码顺序,或者回调是否可信任,亦或是异常处理角度看,都是不尽人意的,下面做简单阐述。</p> <p>顺序性</p> <p>上文例子中代码 funcB 函数,还有两个定时器回调函数,回调内各自又有一个ajax异步请求然后在请求回调里面执行最外层传入的回调函数,对于这类代码,你是否能明确指出个回调的执行顺序呢?如果 funcB 函数内还有异步任务呢?,情况又如何?</p> <p>假如某一天,比如几个月后,线上出了问题,我们需要跟踪异步流,找出问题所在,而跟踪这类异步流,不仅需要理清个异步任务执行顺序,还需要在众多回调函数中不断地跳跃,调试(或许你还能记得诸如 funcB 这些函数的作用和实现),无论是出于效率,可读性,还是出于人性化,都不希望开开发者们再经历这种痛苦。</p> <p>信任问题</p> <p>如上,我们调用了一个第三方支付组件的支付API,进行购买支付,正常情况发现一切运行良好,但是假如某一天,第三方组件出问题了,可能多次调用传入的回调,也可能传回错误的数据。说到底,这样的回调嵌套,控制权在第三方,对于回调函数的调用方式、时间、次数、顺序,回调函数参数,还有下一节将要介绍的异常和错误都是不可控的,因为无论如何,并不总能保证第三方是可信任的。</p> <p>错误处理</p> <p>关于JavaScript错误异常,初中级开发接触的可能并不多,但是其实还是有很多可以学习实践的地方,如前端异常监控系统的设计,开发和部署,并不是三言两语能阐述的,之后会继续推出相关文章。</p> <p>错误堆栈</p> <p>我们知道当JavaScript抛出错误或异常时,对于未捕获异常,浏览器会默认在控制台输出错误堆栈信息,如下,当 test 未定义时:</p> <pre> <code class="language-javascript"> function init(name) { test(name) } init('jh');</code></pre> <p>输出如图:</p> <p><img src="https://simg.open-open.com/show/93020bc7d2b9c5cbbd3b5bbdeb353010.png"></p> <p>如图中自顶向下输出红色异常堆栈信息, Uncaught 表示该异常未捕获, ReferenceError 表明该异常类型为引用异常,冒号后是异常的详细信息: test is not defined , test 未定义;后面以 at 起始的行就是该异常发生处的调用堆栈。第一行说明异常发生在 init 函数,第二行说明 init 函数的调用环境,此处在控制台直接调用,即相当于在匿名函数环境内调用。</p> <p>异步错误堆栈</p> <p>上面例子是同步代码执行的异常,当异常发生在异步任务内时,又会如何呢?,假如把上例中代码放在一个 setTimeout 定时器内执行:</p> <pre> <code class="language-javascript">function init(name) { test(name) } setTimeout(function A() { setTimeout(function() { init(); }, 0); }, 0);</code></pre> <p>如图:</p> <p><img src="https://simg.open-open.com/show/a50e4a2808fd9e45674310315da6f03e.png"></p> <p>可以看到,异步任务中的未捕获异常,也会在控制台输出,但是 setTimeout 异步任务回调函数没有出现在异常堆栈,为什么呢?这是因为当 init 函数执行时, setTimeout 的异步回调函数不在执行栈内,而是通过事件队列调用。</p> <p>JavaScript错误处理</p> <p>JavaScript的异常捕获,主要有两种方式:</p> <ul> <li> <p>try{}catch(e){}主动捕获异常;</p> <p><img src="https://simg.open-open.com/show/7c6afb7465a68762829f2f594e9e6c4e.png"></p> <p>如上,对于同步执行大代码出现异常, try{}catch(e){} 是可以捕获的,那么异步错误呢?</p> <p><img src="https://simg.open-open.com/show/93a9a21f941a4582e3b931ffa212d947.png"></p> <p>如上图,我们发现,异步回调中的异常无法被主动捕获,由浏览器默认处理,输出错误信息。</p> </li> <li> <p>window.onerror事件处理器,所有未捕获异常都会自动进入此事件回调</p> <p><img src="https://simg.open-open.com/show/505317b19a683f394da150c0222418ff.png"></p> <p>如上图,输出了 script error 错误信息,同时,你也许注意到了,控制台依然打印出了错误堆栈信 息,或许你不希望用户看到这么醒目的错误提醒,那么可以使 window.onerror 的回调返回true即可阻止浏览器的默认错误处理行为:</p> <p><img src="https://simg.open-open.com/show/38215c074ee4d1e0cf8e06226235d58c.png"></p> <p>当然,一般不随意设置window.onerror回调,因为程序通常可能需要部署前端异常监控系统,而通常就是使用window.onerror处理器实现全局异常监控,而该事件处理器只能注册一个回调。</p> </li> </ul> <p>回调与Promise</p> <p>以上我们谈到的诸多关于回调的不足,都很常见,所以必须是需要解决的,而Promise正是一种很好的解决这些问题的方式,当然,现在已经提出了比Promise更先进的异步任务处理方式,但是目前更大范围使用,兼容性更好的方式还是Promise,也是本篇要介绍的,之后会继续介绍其他处理方式。</p> <h2>Promises/A+</h2> <p>分析了一大波问题后,我们知道Promise的目标是异步管理,那么Promise到底是什么呢?</p> <ul> <li>异步,表示在将来某一时刻执行,那么Promise也必须可以表示一个将来值;</li> <li>异步任务,可能成功也可能失败,则Promise需要能完成事件,标记其状态值(这个过程即决议-resolve,下文将详细介绍);</li> <li>可能存在多重异步任务,即异步任务回调中有异步任务,所以Promise还需要支持可重复使用,添加异步任务(表现为顺序链式调用,注册异步任务,这些异步任务将按注册的顺序执行)。</li> </ul> <p>所以,Promise是一种封装未来值的易于复用的异步任务管理机制。</p> <p>为了更好的理解Promise,我们介绍一下Promises/A+,一个公开的可操作的Promises实现标准。先介绍标准规范,再去分析具体实现,更有益于理解。</p> <p>Promise代表一个异步计算的最终结果。使用promise最基础的方式是使用它的 then 方法,该方法会注册两个回调函数,一个接收promise完成的最终值,一个接收promise被拒绝的原因。</p> <p>Promises/A</p> <p>你可能还会想问Promises/A是什么,和Promises/A+有什么区别。Promises/A+在 Promises/A议案 的基础上,更清晰阐述了一些准则,拓展覆盖了一些事实上的行为规范,同时删除了一些不足或者有问题的部分。</p> <p>Promises/A+规范目前只关注如何提供一个可操作的 then 方法,而关于如何创建,决议promises是日后的工作。</p> <p>术语</p> <ol> <li>promise: 指一个拥有符合规范的 then 方法的对象;</li> <li>thenable: 指一个定义了 then 方法的对象;</li> <li>决议(resolve): 改变一个promise等待状态至已完成或被拒绝状态, 一旦决议,不再可变;</li> <li>值(value): 一个任意合法的JavaScript值,包括 undefined , thenable 对象, promise 对象;</li> <li>exception/error: JavaScript引擎抛出的异常/错误</li> <li>拒绝原因(reject reason): 一个promise被拒绝的原因</li> </ol> <p>Promise状态</p> <p>一个promise只可能处于三种状态之一:</p> <ul> <li>等待(pending):初始状态;</li> <li>已完成(fulfilled):操作成功完成;</li> <li>被拒绝(rejected):操作失败;</li> </ul> <p>这三个状态变更关系需满足以下三个条件:</p> <ul> <li>处于等待(pending)状态时,可以转变为已完成(fulfilled)或者被拒绝状态(rejected);</li> <li>处于已完成状态时,状态不可变,且需要有一个最终值;</li> <li>处于被拒绝状态时,状态不可变,且需要有一个拒绝原因。</li> </ul> <p>then方法</p> <p>一个promise必须提供一个 then 方法,以供访问其当前状态,或最终值或拒绝原因。</p> <p>参数</p> <p>该方法接收两个参数,如 promise.then(onFulfilled, onRejected) :</p> <ul> <li>两个参数均为可选,均有默认值,若不传入,则会使用默认值;</li> <li>两个参数必须是函数,否则会被忽略,使用默认函数;</li> <li>onFulfilled: 在promise已完成后调用且仅调用一次该方法,该方法接受promise最终值作参数;</li> <li>onRejected: 在promise被拒绝后调用且仅调用一次该方法,该方法接受promise拒绝原因作参数;</li> <li>两个函数都是异步事件的回调,符合 JavaScript事件循环处理流程</li> </ul> <p>返回值</p> <p>该方法必须返回一个promise:</p> <pre> <code class="language-javascript"> var promise2 = promise1.then(onFulfilled, onRejected); // promise2依然是一个promise对象</code></pre> <p>决议过程(resolution)</p> <p>决议是一个抽象操作过程,该操作接受两个输入:一个promise和一个值,可以记为; [[resolve]](promise, x) ,如果x是一个 thenable 对象,则尝试让 promise 参数使用 x 的状态值;否则,将使用 x 值完成传入的 promise ,决议过程规则如下:</p> <ol> <li>如果 promise 和 x 引用自同一对象,则使用一个 TypeError 原因拒绝此 promise ;</li> <li>x 为Promise,则 promise 直接使用 x 的状态;</li> <li>x 为对象或函数: <ol> <li>获取一个 x.then 的引用;</li> <li>若获取 x.then 时抛出异常 e ,使用该 e 作为原因拒绝 promise ;</li> <li>否则将该引用赋值给 then ;</li> <li>若 then 是一个函数,就调用该函数,其作用域为 x ,并传递两个回调函数参数,第一个是 resolvePromise ,第二个是 rejectPromise : <ol> <li>若调用了 resolvePromise(y) ,则执行 resolve(promise, y) ;</li> <li>若调用了 rejectPrtomise(r) ,则使用原因 r 拒绝 promise ;</li> <li>若多次调用,只会执行第一次调用流程,后续调用将被忽略;</li> <li>若调用 then 抛出异常 e ,则: <ol> <li>若 promise 已决议,即调用了 resolvePromise 或 rejectPrtomise ,则忽略此异常;</li> <li>否则,使用原因 e 拒绝 promise ;</li> </ol> </li> </ol> </li> <li>若 then 不是函数,则使用 x 值完成 promise ;</li> </ol> </li> <li>若 x 不是对象或函数,则使用 x 完成 promise 。</li> </ol> <p>自然,以上规则可能存在递归循环调用的情况,如一个 promsie 被一个循环的 thenable 对象链决议,此时自然是不行的,所以规范建议进行检测,是否存在递归调用,若存在,则以原因 TypeError 拒绝 promise 。</p> <h2>Promise</h2> <p>在ES6中,JavaScript已支持Promise,一些主流浏览器也已支持该Promise功能,如Chrome,先来看一个Promsie使用实例:</p> <pre> <code class="language-javascript"> var promise = new Promise((resolve, reject) => { setTimeout(function() { resolve('完成'); }, 10); }); promise.then((msg) => { console.log('first messaeg: ' + msg); }) promise.then((msg) => { console.log('second messaeg: ' + msg); });</code></pre> <p>输出如下:</p> <p><img src="https://simg.open-open.com/show/1c37aa57c6ff8c860557c51f695af460.png"></p> <p>构造器</p> <p>创建promise语法如下:</p> <pre> <code class="language-javascript"> new Promise(function(resolve, reject) {});</code></pre> <ul> <li> <p>参数</p> <p>一个函数,该函数接受两个参数:resolve函数和reject函数;当实例化Promise构造函数时,将立即调用该函数,随后返回一个Promise对象。通常,实例化时,会初始一个异步任务,在异步任务完成或失败时,调用resolve或reject函数来完成或拒绝返回的Promise对象。另外需要注意的是,若传入的函数执行抛出异常,那么这个promsie将被拒绝。</p> </li> </ul> <p>静态方法</p> <p>Promise.all(iterable)</p> <p>all方法接受一个或多个promsie(以数组方式传递),返回一个新promise,该promise状态取决于传入的参数中的所有promsie的状态:</p> <ol> <li>当所有promise都完成是,返回的promise完成,其最终值为由所有完成promsie的最终值组成的数组;</li> <li>当某一promise被拒绝时,则返回的promise被拒绝,其拒绝原因为第一个被拒绝promise的拒绝原因;</li> </ol> <pre> <code class="language-javascript">var p1 = new Promise((resolve, reject) => { setTimeout(function(){ console.log('p1决议'); resolve('p1'); }, 10); }); var p2 = new Promise((resolve, reject) => { setTimeout(function(){ console.log('p2决议'); resolve('p2'); }, 10); }); Promise.all( [p1, p2] ) .then((msgs) => { // p1和p2完成并传入最终值 console.log(JSON.stringify(msgs)); }) .then((msg) => { console.log( msg ); });</code></pre> <p>输出如下:</p> <p><img src="https://simg.open-open.com/show/c5e7bff6c014813de55986b1644b2563.png"></p> <p>Promise.race(iterable)</p> <p>race方法返回一个promise,只要传入的诸多promise中的某一个完成或被拒绝,则该promise同样完成或被拒绝,最终值或拒绝原因也与之相同。</p> <p>Promise.resolve(x)</p> <p>resolve方法返回一个已决议的Promsie对象:</p> <ol> <li>若 x 是一个promise或 thenable 对象,则返回的promise对象状态同 x ;</li> <li>若 x 不是对象或函数,则返回的promise对象以该值为完成最终值;</li> <li>否则,详细过程依然按前文Promsies/A+规范中提到的规则进行。</li> </ol> <p>该方法遵循Promise/A+决议规范。</p> <p>Promsie.reject(reason)</p> <p>返回一个使用传入的原因拒绝的Promise对象。</p> <p>实例方法</p> <p>Promise.prototype.then(onFulfilled, onRejected)</p> <p>该方法为promsie添加完成或拒绝处理器,将返回一个新的promise,该新promise接受传入的处理器调用后的返回值进行决议;若promise未被处理,如传入的处理器不是函数,则新promise维持原来promise的状态。</p> <p>我们通过两个例子介绍 then 方法,首先看第一个实例:</p> <pre> <code class="language-javascript"> var promise = new Promise((resolve, reject) => { setTimeout(function() { resolve('完成'); }, 10); }); promise.then((msg) => { console.log('first messaeg: ' + msg); }).then((msg) => { console.log('second messaeg: ' + msg); });</code></pre> <p>输出如下:</p> <p><img src="https://simg.open-open.com/show/4396be1911b9af69c6d867b250df0463.png"></p> <p>输出两行信息:我们发现第二个 then 方法接收到的最终值是 undefined ,为什么呢?看看第一个 then 方法调用后返回的 promise 状态如下:</p> <p><img src="https://simg.open-open.com/show/170f2ff35b84159eb32bb7d3ef166703.png"></p> <p>如上图,发现调用第一个 then 方法后,返回promise最终值为 undefined ,传递给第二个 then 的回调,如果把上面的例子稍加改动:</p> <pre> <code class="language-javascript"> var promise = new Promise((resolve, reject) => { setTimeout(function() { resolve('完成'); }, 10); }); promise.then((msg) => { console.log('first messaeg: ' + msg); return msg + '第二次'; }).then((msg) => { console.log('second messaeg: ' + msg); });</code></pre> <p>输出如下:</p> <p><img src="https://simg.open-open.com/show/9e7ce31bf63fcd174d538123858da8fd.png"></p> <p>这次两个 then 方法的回调都接收到了最终值,正如我们前文所说,’then’方法返回一个新promise,并且该新promise根据其传入的回调执行的返回值,进行决议,而函数未明确 return 返回值时,默认返回的是 undefined ,这也是上面实例第二个 then 方法的回调接收 undefined 参数的原因。</p> <p>这里使用了链式调用,我们需要明确:共产生三个promise,初始promise,两个then方法分别返回一个promise;而第一个 then 方法返回的新promise是第二个 then 方法的主体,而不是初始promise。</p> <p>Promise.prototype.catch(onRejected)</p> <p>该方法为promise添加拒绝回调函数,将返回一个新promise,该新promise根据回调函数执行的返回值进行决议;若promise决议为完成状态,则新promise根据其最终值进行决议。</p> <pre> <code class="language-javascript"> var promise = new Promise((resolve, reject) => { setTimeout(() => { reject('failed'); }, 0); }); var promise2 = promise.catch((reason) => { console.log(reason); return 'successed'; }); var promise3 = promise.catch((reason) => { console.log(reason); }); var promise4 = promise.catch((reason) => { console.log(reason); throw 'failed 2'; });</code></pre> <p>输出如下图:</p> <p><img src="https://simg.open-open.com/show/de02c7a990d526bd9cd55de764c72830.png"></p> <p>如图中所输出内容,我们需要明白以下几点:</p> <ol> <li>catch 会为 promise 注册拒绝回调函数,一旦异步操作结束,调用了 reject 回调函数,则依次执行注册的拒绝回调;</li> <li>另外有一点和 then 方法相似, catch 方法返回的新promise将使用其回调函数执行的返回值进行决议,如promise2,promise3状态均为完成(resolved),但是promise3最终值为 undefined ,而 promise2 最终值为 successed ,这是因为在调用 promise.catch 方法时,传入的回调没有显式的设置返回值;</li> <li>对于promise4,由于调用 catch 方法时,回调中 throw 抛出异常,所以promise4状态为拒绝(rejected),拒绝原因为抛出的异常;</li> <li>特别需要注意的是这里一共有四个promise,一旦决议,它们之间都是独立的,我们需要明白无论是 then 方法,还是 catch 方法,都会返回一个新promise,此新promise与初始promise相互独立。</li> </ol> <p>catch 方法和 then 方法的第二个参数一样,都是为promise注册拒绝回调。</p> <p>链式调用</p> <p>和jQuery的链式调用一样,Promise设计也支持链式调用,上一步的返回值作为下一步方法调用的主体:</p> <pre> <code class="language-javascript"> new Promise((resolve, reject) => { setTimeout(()=>{ resolve('success'); },0); }).then((msg) => { return 'second success'; }).then((msg) => { console.log(msg); });</code></pre> <p>最后输出: second success ,初始化promise作为主体调用第一个 then 方法,返回完成状态的新promise其最终值为 second success ,然后该新promise作为主体调用第二个 then 方法,该方法返回第三个promise,而且该promise最终值为 undefined ,若不清楚为什么,请回到关于 Promise.prototype.then 和 Promise.prototype.catch 的介绍。</p> <p>错误处理</p> <p>我们前文提到了JavaScript异步回调中的异常是难以处理的,而Promise对异步异常和错误的处理是比较方便的:</p> <pre> <code class="language-javascript"> var promise = new Promise((resolve, reject) => { test(); // 抛出异常 resolve('success'); // 被忽略 }); console.log(promise); promise.catch((reason) => { console.log(reason); });</code></pre> <p>输出如图,执行 test 抛出异常,导致promise被拒绝,拒绝原因即抛出的异常,然后执行 catch 方法注册的拒绝回调:</p> <p><img src="https://simg.open-open.com/show/469e2b1d2f6475be53366e4bbfe7c607.png"></p> <p>决议,完成与拒绝</p> <p>目前为止,关于Promise是什么,我们应该有了一定的认识,这里,需要再次说明的是Promise的三个重要概念及其关系:决议(resolve),完成(fulfill),拒绝(reject)。</p> <ol> <li>完成与拒绝是Promise可能处于的两种状态;</li> <li>决议是一个过程,是Promise由等待状态变更为完成或拒绝状态的一个过程;</li> <li>静态方法 Promise.resolve 描述的就是一个决议过程,而Promise构造函数,传入的回调函数的两个参数:resolve和reject,一个是完成函数,一个是拒绝函数,这里令人疑惑的是为什么这里依然使用resolve而不是fulfill,我们通过一个例子解释这个问题:</li> </ol> <pre> <code class="language-javascript"> var promise = new Promise((resolve, reject) => { resolve(Promise.reject('failed')); }); promise.then((msg) => { console.log('完成:' + msg); }, (reason) => { console.log('拒绝:' + reason); });</code></pre> <p>输出如图:</p> <p><img src="https://simg.open-open.com/show/38fbe89ad089b20f834a1c82d114bdbc.png"></p> <p>上例中,在创建一个Promise时,给 resolve 函数传递的是一个拒绝Promise,此时我们发现promise状态是 rejected ,所以这里第一个参数函数执行,完成的是一个更接近决议的过程(可以参考前文讲述的决议过程),所以命名为 resolve 是更合理的;而第二个参数函数,则只是拒绝该promise:</p> <pre> <code class="language-javascript"> var promise = new Promise((resolve, reject) => { reject(Promise.resolve('success')); }); promise.then((msg) => { console.log('完成:' + msg); }, (reason) => { console.log('拒绝:' + reason); });</code></pre> <p>reject 函数并不会处理参数,而只是直接将其当做拒绝原因拒绝promise。</p> <h2>Promise实现</h2> <p>Promise是什么,怎么样使用就介绍到此,另外一个问题是面试过程中经常也会被提及的:如何实现一个Promise,当然,限于篇幅,我们这里只讲思路,不会长篇大论。</p> <p>构造函数</p> <p>首先创建一个构造函数,供实例化创建promise,该构造函数接受一个函数参数,实例化时,会立即调用该函数,然后返回一个Promise对象:</p> <pre> <code class="language-javascript"> var MyPromise = (() => { var value = undefined; // 当前Promise var tasks = []; // 完成回调队列 var rejectTasks = []; // 拒绝回调队列 var state = 'pending'; // Promise初始为等待态 // 辅助函数,使异步回调下一轮事件循环执行 var nextTick = (callback) => { setTimeout(callback, 0); }; // 辅助函数,传递Promsie的状态值 var ref = (value) => { if (value && typeof value.then === 'function') { // 若状态值为thenable对象或Promise,直接返回 return value; } // 否则,将最终值传递给下一个then方法注册的回调函数 return { then: function(callback) { return ref(callback(value)); } } }; var resolve = (val) => {}; var reject = (reason) => {}; function MyPromise(func) { func(resolve.bind(this), reject.bind(this)); } return MyPromise; });</code></pre> <p>静态方法</p> <p>在实例化创建Promise时,我们会将构造函数的两个静态方法: resolve 和 reject 传入初始函数,接下来需要实现这两个函数:</p> <pre> <code class="language-javascript"> var resolve = (val) => { if (tasks) { value = ref(val); state = 'resolved'; // 将状态标记为已完成 // 依次执行任务回调 tasks.forEach((task) => { value = nextTick((val) => {task[0](self.value);}); }); tasks = undefined; // 决议后状态不可变 return this; } }; var reject = (reason) => { if (tasks) { value = ref(reason); state = 'rejected'; // 将状态标记为已完成 // 依次执行任务回调 tasks.forEach((task) => { nextTick((reason) => {task[1](value);}); }); tasks = undefined; // 决议后状态不可变 return this; } };</code></pre> <p>还有另外两个静态方法,原理还是一样,就不细说了。</p> <p>实例方法</p> <p>目前构造函数,和静态方法完成和拒绝Promise都已经实现,接下来需要考虑的是Promise的实例方法和链式调用:</p> <pre> <code class="language-javascript"> MyPromise.prototype.then = (onFulfilled, onRejected) => { onFulfilled = onFulfilled || function(value) { // 默认的完成回调 return value; }; onRejected = onRejected || function(reason) { // 默认的拒绝回调 return reject(reason); }; if (tasks) { // 未决议时加入队列 tasks.push(onFulfilled); rejectTasks.push(onRejected); } else { // 已决议,直接加入事件循环执行 nextTick(() => { if (state === 'resolved') { value.then(onFulfilled); } else if (state === 'rejected') { value.then(onRejected); } }); } return this; };</code></pre> <p>实例</p> <p>以上可以简单实现Promise部分异步管理功能:</p> <pre> <code class="language-javascript"> var promise = new MyPromise((resolve, reject) => { setTimeout(() => { resolve('完成'); }, 0); }); promise.then((msg) => {console.log(msg);});</code></pre> <p>本篇由回调函数起,介绍了回调处理异步任务的常见问题,然后介绍Promises/A+规范及Promise使用,最后就Promise实现做了简单阐述(之后有机会会详细实现一个Promise),花费一周终于把基本知识点介绍完,下一篇将介绍JavaScript异步与生成器实现。</p> <h3>参考</h3> <ol> <li><a href="/misc/goto?guid=4958866170093509700" rel="nofollow,noindex">Promises/A+ specification</a></li> <li><a href="/misc/goto?guid=4958862299195487029" rel="nofollow,noindex">JavaScript Promise</a></li> </ol> <p> </p> <p>来自:http://blog.codingplayboy.com/2017/05/10/async_promise/</p> <p> </p>