当前位置: 首页 > 后端技术 > Node.js

深入Promise(三)——命名Promise

时间:2023-04-03 14:24:43 Node.js

我们经常会遇到这种情况:比如通过用户名查找并返回用户信息及其关注者。通常有两种方法:1、定义一个外部变量:varusergetUserByName('nswbmw').then((_user)=>{user=_userreturngetFollowersByUserId(user._id)}).then((followers)=>{return{user,followers}})2.使用闭包:getUserByName('nswbmw').then((user)=>{returngetFollowersByUserId(user._id).then((followers)=>{return{user,followers}})})两种实现都很好,但都不漂亮。所以我之前就有了一个想法:同一层的then的参数是之前所有then结果的倒序。它反映在代码中:Promise.resolve().then(function(){returngetUserByName('nswbmw')}).then(function(user){returngetFollowersByUserId(user._id)}).then((followers,user)=>{return{user,followers}})第三个then参数是前两个then结果的倒序,即followers和user。我不会列出更复杂的,比如嵌套的承诺。这个实现的重点是:如何区分then的层次。从appointment的实现中,我们知道每个then都返回一个新的promise,这使得我们无法知道当前的then来自之前嵌套的promise有多深。所以这个想法无法实现。在命名Promise之后,我想到了一个比上面更好的解决方案,即命名Promise:当前then的第一个参数仍然是之前promise的返回值(即兼容Promise/A+规范),后来的参数使用了依赖注入。它反映在代码中:Promise.resolve().then(functionuser(){returngetUserByName('nswbmw')}).then(functionfollowers(_,user){returngetFollowersByUserId(user._id)})。then((_,user,followers)=>{return{user,followers}})上面通过命名then的回调函数(如:user),将回调函数的返回值挂载在promise内部变量上(如:values:{user:'xxx'}),并将parentpromise的值传递给childpromise。then的第二个及后面的参数是通过依赖注入的方式注入的,这就是命名Promise实现的基本思想。我们可以命名Promise构造函数的参数,then回调函数,catch回调函数。所以,我在appointment包的基础上修改并发布了named-appoint包。named-appoint原则:给promise添加name和values属性,name是promise的标识(带Promise构造函数的参数,then回调函数或catch回调函数的名字),values是一个存储所有祖先承诺值的名称和值的对象。当parentpromise的状态改变时,设置parentpromise的values和values(this.values[this.name]=value),然后将values复制到childpromise的values中,并依次传下去。再看一个例子:constassert=require('assert')constPromise=require('named-appoint')newPromise(functionusername(resolve,reject){setTimeout(()=>{resolve('nswbmw')})}).then(functionuser(_,username){assert(_==='nswbmw')assert(username==='nswbmw')return{name:'nswbmw',age:'17'}}).then(functionfollowers(_,username,user){assert.deepEqual(_,{name:'nswbmw',age:'17'})assert(username==='nswbmw')assert.deepEqual(user,{name:'nswbmw',age:'17'})return[{name:'zhangsan',age:'17'},{name:'lisi',age:'18'}]}).then((_,user,followers,username)=>{assert.deepEqual(_,[{name:'zhangsan',age:'17'},{name:'lisi',age:'18'}])assert(username==='nswbmw')assert.deepEqual(user,{name:'nswbmw',age:'17'})assert.deepEqual(followers,[{name:'zhangsan',age:'17'},{name:'lisi',age:'18'}])}).catch(console.error)很明显,命名Promise有一个前面提到的条件是:在同一个promise链上,以下代码:constassert=require('assert')constPromise=require('named-appoint')newPromise(functionusername(resolve,reject){setTimeout(()=>{resolve('nswbmw')})}).then(()=>{returnPromise.resolve().then(functionuser(_,username){assert(username===undefined)return{name:'nswbmw',年龄:'17'}})})。然后(函数(_,用户名,用户){assert.deepEqual(_,{名称:'nswbmw',年龄:'17'})断言(用户名==='nswbmw')assert(user===undefined)}).catch(console.error)最后then打印undefined,因为内部生成了一个新的promise链分支。co和co合并没有变化,如:constPromise=require('named-appoint')constco=require('co')constpromise=Promise.resolve().then(functionuser(){return'nswbmw'}).then(functionfollowers(){return[{name:'zhangsan'},{name:'lisi'}]}).then((_,user,followers)=>{return{user,followers}})co(function*(){console.log(yieldpromise)/*{user:'nswbmw',followers:[{name:'zhangsan'},{name:'lisi'}]}*/}.catch(console.error)顺便做了一个Promise/A++规范,未经授权。“挑剔”的错误处理让我们继续思考。Swift中的错误处理看起来像这样:error")}可以设置catch只捕获特定的异常错误,如果前面的catch没有捕获到错误,那么错误会被最后一个catch捕获。将catch回调函数命名为JavaScript也可以实现类似的功能。我在appointment的基础上修改发布了condition-appoint包。看一个例子:varPromise=require('condition-appoint')Promise.reject(newTypeError('typeerror')).catch(functionSyntaxError(e){console.error('SyntaxError:',e)}).catch(functionTypeError(e){console.error('TypeError:',e)}).catch(function(e){console.error('default:',e)})将被第二个catch捕获捕获,即打印:TypeError:[TypeError:typeerror]修改:varPromise=require('condition-appoint')Promise.reject(newTypeError('typeerror')).catch(functionSyntaxError(e){console.error('SyntaxError:',e)}).catch(functionReferenceError(e){console.error('ReferenceError:',e)}).catch(function(e){console.error('default:',e)})会被第三个catch捕获,即print:default:[TypeError:typeerror]因为没有对应的errorcatch函数,所以最终会被匿名catch捕获。再次修改:varPromise=require('condition-appoint')Promise.reject(newTypeError('typeerror')).catch(functionSyntaxError(e){console.error('SyntaxError:',e)}).catch(function(e){console.error('default:',e)}).catch(functionTypeError(e){console.error('TypeError:',e)})将被第二个catch捕获捕获,即print:default:[TypeError:typeerror]因为是提前被匿名catch方法捕获到的。condition-appoint的实现原理很简单,只需要在appointment的then中添加3行代码即可:Promise.prototype.then=function(onFulfilled,onRejected){...if(isFunction(onRejected)&&this.state===REJECTED){if(onRejected.name&&((this.value&&this.value.name)!==onRejected.name)){returnthis;}}...};判断传入的回调函数名是否与错误名相等,如果不是匿名函数且不相等,则通过returnthis跳过catch语句,即实现值穿透。当然condition-appoint对自定义错误也有效,只要自定义错误设置name属性即可。