[书籍翻译] 《JavaScript并发编程》第三章 使用Promises实现同步 (4)

使用像get()这样的函数,它们不仅始终返回像promise一样的原生类型,而且还封装了一些让人讨厌的异步细节。在我们的代码中处理XMLHttpRequest对象并不令人愉快。我们已经简化了可以返回的各种情况。而不是总是必须为load,error和abort事件创建处理程序,我们只需要关心一个接口 - promise。这就是同步并发原则的全部内容。

错误回调

有两种方法可以对被拒绝的promise做出处理。换句话说,提供错误回调。第一种方法是使用catch()方法,该方法使用单一回调函数。另一种方法是将被拒绝的回调函数作为then()的第二个参数传递。

将then()方法用来处理拒绝回调函数在某些情况下表现的更好,它应该被用来替代catch()函数。第一个场景是编写promises和thenable对象可以互换的代码。catch()方法不是thenable必要的一部分。第二个场景是当我们建立回调链时,我们将在本章后面探讨。

让我们看一些代码,它们比较了两种为promises提供拒绝回调函数的方法:

//这个promise执行器将随机执行完成回调或拒绝回调 function executor(resolve, reject) { cnt++; Math.round(Math.random()) ? resolve(`fulfilled promise ${cnt}`) : reject(`rejected promise ${cnt}`); } //让“log()”和“error()”函数作为简单回调函数 var log = console.log.bind(console), error = console.error.bind(console), cnt = 0; //创建一个promise,然后通过“catch()”方法传入拒绝回调。 new Promise(executor).then(log).catch(error); //创建一个promise,然后通过“then()”方法传入拒绝回调。 new Promise(executor).then(log, error);

我们可以看到这两种方法实际上非常相似。在代码美观上,也没有哪个有真正的优势。然而,当涉及到使用thenables时,then()方法有一个优势,我们后面会看到。但是,由于我们实际上并没有以任何方式使用promise实例,除了添加回调之外,实际上没有必要担心catch()和then()用于注册拒绝回调。

始终响应

Promises最终总是结束于完成状态或拒绝状态。我们通常为每个状态传入不同的回调函数。但是,我们很可能希望为这两个状态执行一些相同的操作。例如,如果使用promise的组件在promise等待时更改状态,我们要确保在完成或拒绝promise后清除状态。

我们可以用这样的方式编写代码:完成和拒绝状态的每个回调都去执行这些操作,或者他们每个都可以调用执行一些公用的清理函数。下面这种方式的示图:

image065.gif

将清理任务分配给promise是否有意义,而不是将其分配给其它个别结果?这样,在解析promise时运行的回调函数专注于它需要对值执行的操作,而拒绝回调则专注于处理错误。让我们看看是否可以使用always()方法编写一些扩展promises的代码:

//在promise原型上扩展使用“always()”方法。 //不管promise是完成还是拒绝,始终会调用给定的函数。 Promise.prototype.always = function(func) { return this.then(func, func); }; //创建promise随机完成或被拒绝。 var promise = new Promise((resolve, reject) => { Math.round(Math.random()) ? resolve('fullfilled') : reject('rejected'); }); //传递promise完成和拒绝回调。 promise.then((value) => { console.log(value); }, (reason) => { console.error(reason); }); //这个回调函数总是会在上面的回调执行之后调用。 promise.always((value) => { console.log('cleaning up...'); });

请注意,在这里顺序很重要。如果我们在then()之前调用always(),那么函数仍然会运行,但它会在
回调提供给then()之前运行。我们实际上可以在then()之前和之后都调用always(),以便在完成或拒绝回调
之前以及之后运行代码。

处理其他promises

到目前为止,我们在本章中看到的大多数promise都是由执行程序函数直接完成的,或者是当值准备完成时从异步操作中调用解析器的结果。像这样传递回调函数实际上非常灵活。例如,执行程序甚至不必执行任何任务,除了将解析器函数存储在某处以便稍后调用它来解析promise。

当我们发现自己处于需要多个值的更复杂的同步场景时,这可能特别有用,这些值已经被传递给调用者。如果我们有处理回调函数,我们就可以处理promise。让我们看看,在存储代码的解析函数的多个promises,使每一个promise都可以在后面处理:

//存储一系列解析器函数的列表。 var resolvers = []; //在执行器中创建5个新的promise, //解析器被推到了“resolvers”数组。 //我们可以给每一个promise执行回调。 for(let i = 0; i < 5; i++) { new Promise(() => { resolvers.push(resolve); }).then((value) => { console.log(`resolved ${i + 1}`, value); }); } //设置一个2s之后延时运行函数, //当它运行时,我们遍历“解析器”数组中的每一个解析器函数, //并且传入一个返回值来调用它。 setTimeout(() => { for(resolver of resolvers) { resolver(true); } }, 2000);

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyjgpw.html