[Effective JavaScript 笔记]第68条:使用promise模式清洁异步逻辑

构建异步API的一种流行的替代方式是使用promise(有时也被称为deferred或future)模式。已经在本章讨论过的异步API使用回调函数作为参数。

downloadAsync(‘file.txt‘,function(file){
  console.log(‘file:‘+file);
});

基于promise的API不接收回调函数作为参数。相反,它返回一个promise对象,该对象通过其自身的then方法接收回调函数。

var p=downloadP(‘file.txt‘);
p.then(function(file){
  console.log(‘file: ‘+file);
});

这里看不出与原先的版本有什么不同。但是promise的力量在于它们的组合性。传递给then方法的回调函数不仅产生影响,也可以产生结果。通过回调函数返回一个值,可以构造一个新的promise。

var fileP=downloadP(‘file.txt‘);
var lengthP=fileP.then(function(file){
  return file.length;
});
lengthP.then(function(length){
  console.log(‘length: ‘+length);
});

理解promise的一种方法是将它理解为表示最终值的对象。它封装了一个还未完成的并发操作,但最终会产生一个结果值。then方法允许我们提供一个代表最终值的一种类型的promise对象,并产生一个新的promise对象来代表最终值的另一种类型,而不管回调函数返回了什么。
从现有的promise中构造新promise的能力带来了很大的灵活性,并且具有一些简单但强大的惯用法。例如,构造一个实用程序来拼接多个promise的结果。

var filesP=join(downloadP(‘file1.txt‘),
               downloadP(‘file2.txt‘),
               downloadP(‘file3.txt‘));
filesP.then(function(files){
  console.log(‘file1:‘+files[0]);
  console.log(‘file2:‘+files[1]);
  console.log(‘file3:‘+files[2]);
});

promise库也经常提供一个叫做when的工具函数,其使用类似。

var fileP1=downloadP(‘file1.txt‘),
    fileP2=downloadP(‘file2.txt‘),
    fileP3=downloadP(‘file3.txt‘);
when([fileP1,fileP2,fileP3],function(files){
  console.log(‘file1:‘+files[0]);
  console.log(‘file2:‘+files[1]);
  console.log(‘file3:‘+files[2]);
});

使promise成为卓越的抽象层级的部分原因是通过then方法的返回值来联系结果,或者通过工具函数如join来构成promise,而不是在并行的回调函数间共享数据结构。本质上是安全的,因为避免了66条中讨论过的数据竞争。即使最小心谨慎的程序员也可能会在保存异步操作的结果到共享的变量或数据结构时犯下简单的错误。

var file1,file2;
downloadAsync(‘file1.txt‘,function(file){
  file1=file;
});
downloadAsync(‘file2.txt‘,function(file){
  file1=file;
});

promise避免这种BUG,简单风格的组合promise避免了修改共享数据。
注意异步逻辑的有序链事实上也可用有序的promise,而不是在62条中展现的笨重的嵌套模式。错误处理会自动地通过promise传播。当你通过promise串联异步操作的集合时,你可以为整个序列提供一个简单的error回调函数,而不是将error回调函数传递给每一步,正如63条中的代码所示。
尽管这样,有时故意创建某些种类的数据竞争是有用的。Promise为些提供了一个很好的机制。例如,一个应用程序可能需要尝试从多个不同的服务器上同时下载同一份文件,而选择最先完成的那个文件。select(或choose)工具函数接收几个promise并产生一个其值是最先完成下载的文件的promise。换句话,几个promise彼此竞争。

var fileP=select(downloadP(‘http://e1.com/file.txt‘),downloadP(‘http://e2.com/file.txt‘),downloadP(‘http://e3.com/file.txt‘));
fileP.then(function(file){
  console.log(‘file: ‘+file);
});

select函数的另一个用途是提供超时来终止长时间的操作。

var fileP=select(downloadP(‘http://e1.com/file.txt‘),timeoutErrorP(2000));
fileP.then(function(file){
  console.log(‘file: ‘+file);
},function(error){
  console.log(‘I/O error or timeout: ‘+error);
});

这里提供error回调函数作为第二个参数给promise的then方法的机制。

提示

  • promise代表最终值,即并行操作完成时最终产生的结果

  • 使用promise组合不同的并行操作
  • 使用promise模式的API避免数据竞争
  • 在要求有意的竞争条件时使用select(也被称为choose)

扩展阅读

Promise https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise

时间: 2024-10-12 18:10:14

[Effective JavaScript 笔记]第68条:使用promise模式清洁异步逻辑的相关文章

[Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法

js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+1}" 反射获取函数源代码的功能很强大,使用函数对象的toString方法有严重的局限性.toString方法的局限性ECMAScript标准对函数对象的toString方法的返回结果(即该字符串)并没有任何要求.这意味着不同的js引擎将产生不同的字符串,甚至产生的字符串与该函数并不相关. 如果函数

[Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码

函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式传递给eval函数以达到同样的功能.程序员面临一个选择:应该将代码表示为函数还是字符串?毫无疑问,应该将代码表示为函数.字符串表示代码不够灵活的一个重要原因是:它们不是闭包. 闭包回顾 看下面这个图 js的函数值包含了比调用它们时执行所需要的代码还要多的信息.而且js函数值还在内部存储它们可能会引用

[Effective JavaScript 笔记] 第4条:原始类型优于封闭对象

js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //"number" typeof "s";//"string" typeof null;//"object":ECMAScript把null描述为独特的类型,但返回值却是对象类型,有点困惑. 可以使用Object.prototype.t

[Effective JavaScript 笔记]第46条:使用数组而不要使用字典来存储有序集合

对象属性无序性 js对象是一个无序属性集合. var obj={}; obj.a=10; obj.b=30; 属性a和属性b并没有谁前谁后之说.for...in循环,先输出哪个属性都有可能.获取和设置不同的属性与顺序无关,都会以大致相同的效率产生相同的结果.也就是说访问属性a和访问属性b,没有哪个访问更快之说.ES标准并未规定属性存储的任何特定顺序,甚至于枚举对象也未涉及.for...in循环会挑选一定的顺序来枚举对象的属性,标准允许js引擎自由选择一个顺序,它们的选择会微妙地改变程序行为.如要

[Effective JavaScript 笔记]第15条:当心局部块函数声明笨拙的作用域

嵌套函数声明.没有标准的方法在局部块里声明函数,但可以在另一个函数的顶部嵌套函数声明. function f(){return "global"} function test(x){ var result=[]; function f(){return "local";}//block-local if(x){ result.push(f()); } result.push(f()); return result; } test(true);//["loc

[Effective JavaScript 笔记]第45条:使用hasOwnProperty方法以避免原型污染

之前的43条,44条讨论了属性的枚举,但都没有彻底地解决属性查找中原型污染的问题.看下面关于字典的一些操作 'zhangsan' in dict; dict.zhangsan; dict.zhangsan=22; js的对象操作总是经继承的方式工作的.即使是一个空的对象字面量也是继承了Object.protoype属性. var dict={}; 'zhangsan' in dict;//false 'lisi' in dict;//false 'wangwu' in dict;//false'

[Effective JavaScript 笔记] 第6条:了解分号插入的局限

分号可以省略 js可以在语句结束不强制加分号.(建议还是添加,不添加分号往往会出现不易发现的BUG) function Point(x,y){ this.x=x||0; this.y=y||0; } Point.prototype.isOrigin=function(){ return this.x===0 && this.y===0 } 上面代码可以运行,是由于js可以自动插入分号,它是一种程序解析技术.能推断出某些上下文中省略的分号,然后有效地自动地将分号"插入"到

[Effective JavaScript 笔记]第67条:绝不要同步地调用异步的回调函数

设想有downloadAsync函数的一种变种,它持有一个缓存(实现为一个Dict)来避免多次下载同一个文件.在文件已经被缓存的情况下,立即调用回调函数是最优选择. var cache=new Dict(); function downloadCachingAsync(url,onsuccess,onerror){ if(cache.has(url)){ onsuccess(cache.get(url)); return; } return downloadAsync(url,function(

[Effective JavaScript 笔记]第60条:支持方法链

无状态的API的部分能力是将复杂操作分解为更小的操作的灵活性.一个很好的例子是字符串的replace方法.由于结果本身也是字符串,可以对前一个replace操作重复执行替换.这种模式的一个常见用例是在将字符串插入到HTML前替换字符串的特殊字符字母. function escapeBasicHTML(str){ return str.replace(/&/g,"&") .replace(/< /g,"<") .replace(/>/