1. 首页
  2. IT资讯

「JavaScript基础」Promise使用指南

“u003Cdivu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fdfic-imagehandleru002F35f1ad04-bc02-4e02-b510-f8d4dea28445″ img_width=”1023″ img_height=”682″ alt=”「JavaScript基础」Promise使用指南” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003E在上篇文章里u003Ca class=”pgc-link” data-content=”mp” href=”https:u002Fu002Fwww.toutiao.comu002Fi6711564903414497795u002F?group_id=6711564903414497795″ target=”_blank”u003E「JavaScript基础」回调(callback)是什么u003Cu002Fau003E我们一起学习了回调,明白了回调就是一个在另外一个函数执行完后要执行的函数,如果我们希望异步函数能够像同步函数那样顺序执行,只能嵌套使用回调函数,过多的回调嵌套会使得代码变得难以理解与维护,为了避免“回调地狱”让人发狂的行为,ES6原生引入了promise的模式,通过这种方式,让我们代码看起来像同步代码,大大简化了异步编程,简直是ES6新特性中最让我们兴奋的特性之一。u003Cu002Fpu003Eu003Ch1u003E什么是promise?u003Cu002Fh1u003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fdfic-imagehandleru002F080842cb-1f04-42ef-9d82-b29a9da23b1d” img_width=”1200″ img_height=”798″ alt=”「JavaScript基础」Promise使用指南” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003E首先我们看看promise这个单词的中文释义,作为名称解释为承诺、诺言、誓言、约言,从中文释义可以看出,是一个未发生,将来一定会发生的某种东东…… 接下来我们来看看ECMA委员会怎么定义Promise的:u003Cu002Fpu003Eu003Cblockquoteu003Eu003Cpu003EA Promise is an object that is used as a placeholder for the eventual results of a deferred (and possibly asynchronous) computation.u003Cu002Fpu003Eu003Cu002Fblockquoteu003Eu003Cpu003EPromise是一个对象,用作占位符,用于延迟(可能是异步)计算的最终结果。u003Cu002Fpu003Eu003Cpu003E简单的来说,promise就是装载未来值的容器。其实生活中有很多Promise的场景,设想以下的场景:我们去快餐店点餐,你点了一份牛肉面,你扫码付款后,会拿到一份带订单号的收据。订单号就是快餐店给我们的一份牛肉面的承诺(promise),保证了我们得到一份牛肉面。u003Cu002Fpu003Eu003Cpu003E所以我们一定要包管好我们的订单收据,因为我们知道了这个收据代表了我们未来会有一份牛肉面,尽管快餐店不能马上给我们一份牛肉面,但是我们大脑潜意识的把订单收据当做牛肉面的“占位符”了u003Cu002Fpu003Eu003Cpu003E终于,我们听到服务员在喊“100号的牛肉面好了,请到窗口取餐”,然后我们拿着订单收据来到窗口递给服务员,我们换来了牛肉面。u003Cu002Fpu003Eu003Cpu003E说了这么多,简单点就是,一旦我需要的值准备好了,我们就用我的承诺值换取这个值本身。u003Cu002Fpu003Eu003Cpu003E但是,还有一种结果,服务员叫到我们的订单号,当我们去拿的时候,服务员会一脸歉意的告诉我们“十分抱歉,您的牛肉面卖完了”。作为顾客的我们对这个情况,除了愤怒之外只能换个地吃饭了或者点其他的。从中我们看出,未来值还有一个重要的特性:它可能成功也可能失败。u003Cu002Fpu003Eu003Cpu003E生活的例子很简单,我们是不是特别着急如何用代码编写Promise呢,在编写代码之前,我们还是先聊聊——Promise State(承诺状态,注:暂且这么翻译,小编也不知道如何翻译更好)u003Cu002Fpu003Eu003Ch1u003EPromise State(承诺状态)u003Cu002Fh1u003Eu003Cpu003EPromise只会处在以下状态之一:u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003EPending(待处理): u003Cu002Fstrongu003Epromise初始化的状态,正在运行,既未完成也没有失败的状态,此状态可以迁移至fulfilled和rejected状态。u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003EFulfilled(已完成)u003Cu002Fstrongu003E:如果回调函数实现Promise的resolve回调(稍后介绍),那我们的promise实现兑现。u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003ERejected(已拒绝):u003Cu002Fstrongu003E如果Promise调用过程中遭到拒绝或者发生异常,那么我们的promise被拒绝,处于Rejected(状态)。u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003ESettled(不变的):Promise如果不处在Pending状态,状态就会改变,要不是Fulfilled要不是Rejected这两种状态。u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003EPromise的状态转换,可以用下面一张图进行表示(图片来源:https:u002Fu002Fdeveloper.mozilla.orgu002Fen-USu002Fdocsu002FWebu002FJavaScriptu002FReferenceu002FGlobal_Objectsu002FPromise#Methods)u003Cu002Fpu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002F6a43ccc27b5c484a96dad18315020543″ img_width=”801″ img_height=”297″ alt=”「JavaScript基础」Promise使用指南” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Ch1u003EPromise vs callbacku003Cu002Fh1u003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fdfic-imagehandleru002F03538628-6c9c-4aa6-ba43-88af0d51b6f2″ img_width=”1200″ img_height=”756″ alt=”「JavaScript基础」Promise使用指南” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003E比如我们有个需求,需要通过AJAX实现三个请求,第二个和第三个请求都依赖上一个接口的请求,如果使用CallBack的方式,我们的代码可能是这样的:u003Cu002Fpu003Eu003Cpreu003EajaxCall(‘http:u002Fu002Fexample.comu002Fpage1’, response1 => { u003Cbru003E ajaxCall(‘http:u002Fu002Fexample.comu002Fpage2’+response1, response2 => { u003Cbru003E ajaxCall(‘http:u002Fu002Fexample.comu002Fpage3’+response2, response3 => { u003Cbru003E console.log(response3) u003Cbru003E } u003Cbru003E }) u003Cbru003E})u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E你很快就会发现,这种多重嵌套的代码不但难以理解,而且难以维护,这就是著名的“回调地狱”现象。u003Cu002Fpu003Eu003Cpu003E如果使用Promise则会让我们的大脑更容易接受和理解,代码显得简单扁平化,代码如下(伪代码),如何实现ajaxCallPromise稍后介绍:u003Cu002Fpu003Eu003Cpreu003EajaxCallPromise(‘http:u002Fu002Fexample.comu002Fpage1’) u003Cbru003E.then( response1 => ajaxCallPromise(‘http:u002Fu002Fexample.comu002Fpage2’+response1) ) u003Cbru003E.then( response2 => ajaxCallPromise(‘http:u002Fu002Fexample.comu002Fpage3’+response2) ) u003Cbru003E.then( response3 => console.log(response3) )u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E你是不是觉得代码的复杂性突然降低,代码看起来更简单易读呢,各位是不是特别着急ajaxCallPromise是如何实现的吧,接着看下一小节,会有介绍。u003Cu002Fpu003Eu003Ch1u003EPromise实现——(resolve, reject) 方法u003Cu002Fh1u003Eu003Cpu003E要实现回调函数转换成Promise,我们需要使用Promise构造函数,在上一小节,我们的示例ajaxCallPromise函数返回Promise,我们可以自定义resolve实现和reject实现,实现函数如下:u003Cu002Fpu003Eu003Cpreu003Econst ajaxCallPromise = url => { u003Cbru003E return new Promise((resolve, reject) => { u003Cbru003E u002Fu002F DO YOUR ASYNC STUFF HERE u003Cbru003E $.ajaxAsyncWithNativeAPI(url, function(data) { u003Cbru003E if(data.resCode === 200) { u003Cbru003E resolve(data.message) u003Cbru003E } else { u003Cbru003E reject(data.error) u003Cbru003E } u003Cbru003E }) u003Cbru003E }) u003Cbru003E}u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E如何理解这段代码呢?u003Cu002Fpu003Eu003Colu003Eu003Cliu003E首先定义ajaxCallPromise返回类型为Promise,这意味我们会实现一个Promise的承诺。u003Cu002Fliu003Eu003Cliu003EPromise接受两个函数参数,resolve(成功实现承诺)和reject(异常导致失败)u003Cu002Fliu003Eu003Cliu003Eresolve和reject这两个特有的方法,会获取对应成功或失败的值u003Cu002Fliu003Eu003Cliu003E如果接口请求一切正常,我们将会接收到resolve函数返回的值u003Cu002Fliu003Eu003Cliu003E如果接口请求失败,我们将会接收到reject返回的的值u003Cu002Fliu003Eu003Cu002Folu003Eu003Cpu003E在举个简单的例子,如果foo()和bar()函数都实现promise,我们怎么书写代码呢?u003Cu002Fpu003Eu003Cpu003E方式一:u003Cu002Fpu003Eu003Cpreu003Efoo().then( res => { u003Cbru003E bar().then( res2 => { u003Cbru003E console.log(‘Both done’) u003Cbru003E }) u003Cbru003E})u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E方式二(建议这种,简单易读)u003Cu002Fpu003Eu003Cpreu003Efoo() u003Cbru003E.then( res => bar() ) u002Fu002F bar() returns a Promise u003Cbru003E.then( res => { u003Cbru003E console.log(‘Both done’) u003Cbru003E})u003Cbru003Eu003Cu002Fpreu003Eu003Ch1u003E.then(onFulfilled, onRejected) 方法u003Cu002Fh1u003Eu003Cpu003EPromise的then()方法允许我们在任务完成后或拒绝失败后执行任务,该任务可以是基于另外一个事件或基于回调的异步操作。u003Cu002Fpu003Eu003Cpu003EPromise的then()方法接收两个参数,即onFulfilled 和 onRejected 的回调,如果Promise对象完成,则执行onFulfilled回调,如果执行异常或失败执行onRejected回调。u003Cu002Fpu003Eu003Cpu003E简单的来说,onFulfilled回调接收一个参数,及所谓的未来的值,同样 onRejected 也接收一个参数,显示拒绝的原因。让我们动下上小节ajaxCallPromise的then()方法:u003Cu002Fpu003Eu003Cpreu003EajaxCallPromise(‘http:u002Fu002Fexample.comu002Fpage1’).then( u003Cbru003E successData => { console.log(‘Request was successful’) }, u003Cbru003E failData => { console.log(‘Request failed’ + failData) } u003Cbru003E)u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E如果请求过程失败,第二个函数将会执行输出而不是第一个函数输出。u003Cu002Fpu003Eu003Cpu003E我们一起再来看个简单的例子,我们在setTimeout()实现Promise回调,代码如下:u003Cu002Fpu003Eu003Cpreu003Econst PsetTimeout = duration => { u003Cbru003E return new Promise((resolve, reject) => { u003Cbru003E setTimeout( () => { u003Cbru003E resolve() u003Cbru003E }, duration); u003Cbru003E }) u003Cbru003E} u003Cbru003EPsetTimeout(1000) u003Cbru003E.then(() => { u003Cbru003E console.log(‘Executes after a second’) u003Cbru003E})u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E这里我们在这里实现了一个成功状态后没有返回值的Promise,如果这样做,成功返回后未来值将会是 undefined.u003Cu002Fpu003Eu003Ch1u003Ecatch(onRejected)方法u003Cu002Fh1u003Eu003Cpu003E除了then()方法可以处理错误和异常,使用Promise的catch()方法也能实现同样的功能,这个方法其实并没有什么特别,只是更容易理解而已。u003Cu002Fpu003Eu003Cpu003Ecatch()方法只接收一个参数,及onRejected回调。catch()方法的onRejected回调的调用方式与then()方法的onRejected回调相同。u003Cu002Fpu003Eu003Cpu003E还记得我们上小节ajaxCallPromise的then()方法的实现吗:u003Cu002Fpu003Eu003Cpreu003EajaxCallPromise(‘http:u002Fu002Fexample.comu002Fpage1’).then( u003Cbru003E successData => { console.log(‘Request was successful’) }, u003Cbru003E failData => { console.log(‘Request failed’ + failData) } u003Cbru003E)u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E我们还可以使用catch()方法进行捕获异常或拒绝,效果是一致的。u003Cu002Fpu003Eu003Cpreu003EajaxCallPromise(‘http:u002Fu002Fexample.comu002Fpage1’) u003Cbru003E.then(successData => console.log(‘Request was successful’)) u003Cbru003E.catch(failData => console.log(‘Request failed’ + failData));u003Cbru003Eu003Cu002Fpreu003Eu003Ch1u003EPromise.resolve(value)方法u003Cu002Fh1u003Eu003Cpu003EPromise的resolve()方法接收成功返回值并返回一个Promise对象,用于未来值的传递,将值传递给.then(onFulfilled, onRejected) 的onFulfilled回调中。resolve()方法可以用于将未来值转化成Promise对象,下面的一段代码演示了如何使用Promise.resolve()方法:u003Cu002Fpu003Eu003Cpreu003Econst p1 = Promise.resolve(4); u003Cbru003Ep1.then(function(value){ u003Cbru003E console.log(value); u003Cbru003E}); u002Fu002Fpassed a promise object u003Cbru003EPromise.resolve(p1).then(function(value){ u003Cbru003E console.log(value); u003Cbru003E}); u003Cbru003EPromise.resolve({name: “Eden”}) u003Cbru003E.then(function(value){ u003Cbru003E console.log(value.name); u003Cbru003E});u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E控制台将会输出以下内容:u003Cu002Fpu003Eu003Cpreu003E4 u003Cbru003E4 u003Cbru003EEdenu003Cbru003Eu003Cu002Fpreu003Eu003Ch1u003EPromise.reject(value)方法u003Cu002Fh1u003Eu003Cpu003EPromise.reject(value)方法可上小节Promise.resolve(value)类似,唯一不同的是将值传递给.then(onFulfilled, onRejected) 的onRejected回调中,同时Promise.reject(value)主要用来进行调试,而不是将值转换成Promise对象。u003Cu002Fpu003Eu003Cpu003E让我们看看下面一段代码,看看如何使用Promise.reject(value)方法:u003Cu002Fpu003Eu003Cpreu003Econst p1 = Promise.reject(4); u003Cbru003Ep1.then(null, function(value){ u003Cbru003Econsole.log(value); u003Cbru003E}); u003Cbru003EPromise.reject({name: “Eden”}) u003Cbru003E.then(null, function(value){ u003Cbru003E console.log(value.name); u003Cbru003E});u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E控制台将输出:u003Cu002Fpu003Eu003Cpreu003E4 u003Cbru003EEdenu003Cbru003Eu003Cu002Fpreu003Eu003Ch1u003EPromise.all(iterable) 方法u003Cu002Fh1u003Eu003Cpu003E该方法可以传入一个可以迭代Promise对象,比如数组并返回一个Promise对象,当所有的Promise迭代对象成功返回后,整个Promise才能返回成功状态的值。u003Cu002Fpu003Eu003Cpu003E好了,我们一起看看怎么实现Promise.all(iterable) 方法:u003Cu002Fpu003Eu003Cpreu003Econst p1 = new Promise(function(resolve, reject){ u003Cbru003E setTimeout(function(){ u003Cbru003E resolve(); u003Cbru003E }, 1000); u003Cbru003E}); u003Cbru003Econst p2 = new Promise(function(resolve, reject){ u003Cbru003EsetTimeout(function(){ u003Cbru003E resolve(); u003Cbru003E }, 2000); u003Cbru003E}); u003Cbru003Econst arr = [p1, p2]; u003Cbru003EPromise.all(arr).then(function(){ u003Cbru003Econsole.log(“Done”); u002Fu002F”Done” is logged after 2 seconds u003Cbru003E});u003Cbru003Eu003Cu002Fpreu003Eu003Cpu003E特别需要注意的一点,只要迭代数组中,只要任意一个进入失败状态,那么该方法返回的对象也会进入失败状态,并将那个进入失败状态的错误信息作为自己的错误信息,示例代码如下:u003Cu002Fpu003Eu003Cpreu003Econst p1 = new Promise(function(resolve, reject){ u003Cbru003E setTimeout(function(){ u003Cbru003E reject(“Error”); u003Cbru003E }, 1000); u003Cbru003E}); u003Cbru003Econst p2 = new Promise(function(resolve, reject){ u003Cbru003E setTimeout(function(){ u003Cbru003E resolve(); u003Cbru003E }, 2000); u003Cbru003E}); u003Cbru003Econst arr = [p1, p2]; u003Cbru003EPromise.all(arr).then(null, function(reason){ u003Cbru003Econsole.log(reason); u002Fu002F”Error” is logged after 1 second u003Cbru003E});u003Cbru003Eu003Cu002Fpreu003Eu003Ch1u003EPromise.race(iterable) 方法u003Cu002Fh1u003Eu003Cpu003E与Promise.all(iterable) 不同的是,Promise.race(iterable) 虽然也接收包含若干个Promise对象的可迭代对象,不同的是这个方法会监听所有的Promise对象,并等待其中的第一个进入完成或失败状态的Promise对象,一旦有Promise对象满足,整个Promise对象将返回这个Promise对象的成功状态或失败状态,下面的示例展示了返回第一个成功状态的值:u003Cu002Fpu003Eu003Cpreu003Evar p1 = new Promise(function(resolve, reject){ u003Cbru003EsetTimeout(function(){ u003Cbru003Eresolve(“Fulfillment Value 1”); u003Cbru003E}, 1000); u003Cbru003E}); u003Cbru003Evar p2 = new Promise(function(resolve, reject){ u003Cbru003EsetTimeout(function(){ u003Cbru003Eresolve(“fulfillment Value 2”); u003Cbru003E}, 2000); u003Cbru003E}); u003Cbru003Evar arr = [p1, p2]; u003Cbru003EPromise.race(arr).then(function(value){ u003Cbru003Econsole.log(value); u002Fu002FOutput “Fulfillment value 1” u003Cbru003E}, function(reason){ u003Cbru003Econsole.log(reason);u003Cbru003Eu003Cu002Fpreu003Eu003Ch1u003E用Promise改写上篇文章的回调方法u003Cu002Fh1u003Eu003Cpu003E读过《JavaScript基础——回调(callback)是什么》文章同学,文章的最后我们用回调函数实现了一个真实的业务场景——用NodeJs实现从论坛帖子列表中显示其中的一个帖子的信息及留言列表信息,如果使用本篇文章学习到的内容,我们如何实现呢, 代码如下:u003Cu002Fpu003Eu003Cpu003Eindex.jsu003Cu002Fpu003Eu003Cpreu003Econst fs = require(‘fs’); u003Cbru003Econst path = require(‘path’); u003Cbru003Econst postsUrl = path.join(__dirname, ‘dbu002Fposts.json’); u003Cbru003Econst commentsUrl = path.join(__dirname, ‘dbu002Fcomments.json’); u003Cbru003Eu002Fu002Freturn the data from our file u003Cbru003Efunction loadCollection(url) { u003Cbru003E return new Promise(function(resolve, reject) { u003Cbru003E fs.readFile(url, ‘utf8’, function(error, data) { u003Cbru003E if (error) { u003Cbru003E reject(‘error’); u003Cbru003E } else { u003Cbru003E resolve(JSON.parse(data)); u003Cbru003E } u003Cbru003E }); u003Cbru003E }); u003Cbru003E} u003Cbru003Eu002Fu002Freturn an object by id u003Cbru003Efunction getRecord(collection, id) { u003Cbru003E return new Promise(function(resolve, reject) { u003Cbru003E const data = collection.find(function(element){ u003Cbru003E return element.id == id; u003Cbru003E }); u003Cbru003E resolve(data); u003Cbru003E }); u003Cbru003E} u003Cbru003Eu002Fu002Freturn an array of comments for a post u003Cbru003Efunction getCommentsByPost(comments, postId) { u003Cbru003E return comments.filter(function(comment){ u003Cbru003E return comment.postId == postId; u003Cbru003E }); u003Cbru003E} u003Cbru003Eu002Fu002Finitialization code u003Cbru003EloadCollection(postsUrl) u003Cbru003E.then(function(posts){ u003Cbru003E return getRecord(posts, “001”); u003Cbru003E}) u003Cbru003E.then(function(post){ u003Cbru003E console.log(post); u003Cbru003E return loadCollection(commentsUrl); u003Cbru003E}) u003Cbru003E.then(function(comments){ u003Cbru003E const postComments = getCommentsByPost(comments, “001”); u003Cbru003E console.log(postComments); u003Cbru003E}) u003Cbru003E.catch(function(error){ u003Cbru003E console.log(error); u003Cbru003E});u003Cbru003Eu003Cu002Fpreu003Eu003Ch1u003E结束语:u003Cu002Fh1u003Eu003Cpu003E本篇的内容就介绍到这里,各位是否看的很过瘾,虽然Promise已经比回调函数好许多,但是还是不够简洁,不够符合我们人类大脑思考逻辑,如果我们能以书写同步的方法书写异步代码,那该多好啊,ES8引入了asyncu002Fawait让我们能用同步的方式书写异步代码,想想就很激动,小编将会在下篇文章进行介绍,敬请期待u003Cu002Fpu003Eu003Cpu003E更多精彩内容,请微信关注u003Cstrongu003E“前端达人”公众号u003Cu002Fstrongu003E!u003Cu002Fpu003Eu003Cu002Fdivu003E”

原文始发于:「JavaScript基础」Promise使用指南

主题测试文章,只做测试使用。发布者:杀手梦三刀,转转请注明出处:http://www.cxybcw.com/10784.html

联系我们

13687733322

在线咨询:点击这里给我发消息

邮件:1877088071@qq.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code