文档章节

ECMAScript 6 Promises(下):谈谈 API(一)

一配
 一配
发布于 2015/12/01 22:52
字数 2682
阅读 205
收藏 2
点赞 0
评论 0

原文地址: http://www.2ality.com/2014/10/es6-promises-api.html
原文作者:Dr. Axel Rauschmayer
译者:倪颖峰

原博客已经标明:本博客文档已经过时,可进一步阅读“Exploring ES6”中的 “Promises for asynchronous programming”。仔细对比了下,两者的确存在一些差异。本文是在原来的译文基础上修订的。

(文章第二部分实在是太长,所以在此分成两部分翻译)

本文是通过普通的Promises 和 ES6的 Promise API 来介绍异步编程。这是两篇系列文章的第二部分 - 第一部分介绍了一下异步编程的基础(你需要充分理解一下以便明白这篇文章)。

1. 概述

    下面函数通过一个Promise异步返回结果:

function asyncFunc() {
    return new Promise(
        function (resolve, reject) {
            resolve(value); // success
            ···
            reject(error); // failure
        });}

可以像下面这样来调用asyncFunc()

asyncFunc()
.then(value => { /* success */ })
.catch(error => { /* failure */ });

1.1 处理Promises数组

Promise.all()可以遍历一个Promises数组。

例如,可以通过数组方法map( )来创建一个Promises数组:

let fileUrls = [
    'http://example.com/file1.txt',
    'http://example.com/file2.txt'
];
let promisedTexts = fileUrls.map(httpGet); // Array of Promises

如果对该数组应用Promise.all( ),那么当所有的Promises被填充后将得到一个数组:

Promise.all(promisedTexts)
// Success
.then(texts => {
    for (let text of texts) {
        console.log(text);
    }})
// Failure
.catch(reason => {
    // Receives first rejection among `promisedTexts`
});

2. Promises

    Promises 是一种解决特定异步编程的模式:函数(或者方法)异步返回其结果。为实现该功能,返回结果为具有占位符意义的一个对象Promise。函数的调用者注册回调函数,一旦结果运算完毕就立即通知Promise。函数会由Promise 来传递结果。
    JavaScript 的Promises 事实标准称为 Promises/A+。ES6 的Promise API 便遵循这个标准。

3. 第一个实例

    看一下第一个实例,来了解下 Promises 是如何运行的。
    NodeJS 风格的回调函数,异步读取文件如下所示:

fs.readFile('config.json', 
    function (error, text) {
        if (error) {
            console.error('Error while reading config file');
        } else {
            try {
                let obj = JSON.parse(text);
                console.log(JSON.stringify(obj, null, 4));
            } catch (e) {
                console.error('Invalid JSON in file');
        }
    }
});

    使用Promises,相同功能的实现可以是这样: 

readFilePromisified('config.json')
.then(function (text) { // (A)
    let obj = JSON.parse(text);
    console.log(JSON.stringify(obj, null, 4));
})
.catch(function (reason) { // (B)
    // File read error or JSON SyntaxError
    console.error('An error occurred', reason);
});

    这里依旧是有回调函数,但这里通过方法来提供的,是在有结果时(then() 和 catch())被调用的。在 B 处的报错的回调函数有两方面的优势:第一,这是一种单一风格的错误处理(与前一个例子中if(error) 和try-catch代码对比下)。 第二,你可以一个代码地点同时处理  readFilePromisified() 的错误 和 A 处回调函数的错误。

    readFilePromisified()函数的实现代码见后面。

4. Promises 的创建和使用

    从生成者和消耗者两方面来看一下Promises 是如何操作的。

4.1. 生成一个Promise

    作为一个生成者,你创建一个Promise 然后用它传递结果:

let promise = new Promise( 
    function(resolve, reject){ // (A)
        ...
        if( ... ){
            resolve( value );
        } else {
            reject( reason );
        }
});

    一个Promise 一般处于以下三个(互斥)状态中的某一个状态: 

     Pending:还没有计算出结果 
     Fulfilled:成功计算出结果
     Rejected:在计算过程中发生一个错误 

    一个Promise 被设置后(settled:代表运算已经完成 ),它的状态要么是 fulfilled 要么是 rejected。每一个 Promise 只能设置一次,然后保持settled状态。之后再设置它将不起作用。

    
    
    new Promise() 的参数( 在 A 处开始的 )称为  executor(执行器):
    1. 如果运算成功,执行器会通过resolve()传递结果,这通常会fufill Promise(后面将会解释,如果resolve的是一个 Promise可能会不同,见后面解释)。
    2. 如果错误发生了,执行器就会通过 reject() 通知Promise 消费者,就会reject Promise。

4.2. 使用Promise

    作为Promise 的消费者,你会通过reactions - 利用 then( )方法注册的回调函数,得到fufillment或者rejection的通知 。

promise.then( 
    function( value ){/* fulfillment */}, 
    function( reason ){/* rejection */} 
);

    正是由于一个Promise 一旦被设置后(settled)再也不能变化,使得Promises 对于异步函数来说非常有用(一次性使用结果)。此外,永远不会有任何竞争条件,因为不论Promise是在设置前还是在设置后调用 then( ) 都是一样的: 

    1. 如果是在Promise设置前注册的reactions, 那么一旦设置将得到通知。
    2. 如果是在Promise设置后注册的reactions, 那么会立即收到缓存的设置的值(像任务那样排队被激活)。 

4.3. 只处理fullfillment或者rejection

    如果你只关心fullfillment,你可以忽略 then() 的第二个参数:

promise.then(
    function( value ){/* fulfillment */}
);

    如果你只对rejection感兴趣,可以忽略第一个参数。也可以更紧凑的 catch() 方法来实现。

promise.then( 
    null, 
    function( reason ){/* rejection */} 
);

// 等价于
promise.catch( 
     function( reason ){/* rejection */} 
);

    这里推荐只用 then() 处理fullfillment,使用 catch() 处理错误,因为这样可以更加优雅的标记回调函数,并且可以同时处理多个Promises 的rejection(稍后解释)。

5. 举例

    在深入探究Promises之前,先使用前面学到的知识来看一些例子。

5.1 举例:fs.readFile() Promise化

下面代码是将Node.js中的函数fs.readFile()利用Promise来重写: 

import {readFile} from 'fs';

function readFilePromisified(filename) {
    return new Promise(
        function (resolve, reject) {
            readFile(filename, { encoding: 'utf8' },
                (error, data) => {
                    if (error) {
                        reject(error);
                    } else {
                        resolve(data);
                    }
            });
    });
}

readFilePromisified()的调用如下:

readFilePromisified(process.argv[2])
.then(text => {
    console.log(text);
})
.catch(error => {
    console.log(error);
});

5.2 举例:XMLHttpRequest promise化

    下面是一个基于 XMLHttpRequest API 事件,通过Promise 编写的 HTTP GET函数。

function httpGet( url ){
    return new Promise( 
        function( resolve, reject ){
            let request = new XMLHttpRequest();
            request.onreadystatechange = function(){
                if( this.status === 200 ){
                    // success
                    resolve( this.response );
                }else { 
                    reject( new Error( this.statusText ) );
                }
            }
            request.onerror = function(){
                reject( new Error('XMLHttpRequest Error: ' + this.statusText ) );
            };
            request.open( 'GET', url );
            request.send();
        });
}

   下面是如何使用 httpGet(): 

httpGet("http://example.com/file.txt")
.then(
    function (value){
        console.log('contents: '+ value);
    }, 
    function (reason){
        console.log('something error', reason);
    }
);

5.3 举例:延迟活动

    使用Promise 的 delay( )函数(类似于Q.delay())来实现 setTimeout( ) 。

function delay (ms){
    return new Promise(function(resolve, reject){
        setTimeout(resolve, ms); // (A)
    });
}

// 使用 delay()
delay(5000).then(function(){ // (B)
    console.log('5s have passed');
});

    注意 A 处我们调用 resolve 没有传递参数,相当于调用了resolve( undefined )。在 B 处我们不需要通过的返回值,就可以简单的忽略它。仅仅通知就已经OK了。 

5.4 举例:Promise超时

function timeout(ms, promise){
    return new Promise(function(resolve, reject){
        promise.then( resolve );
        setTimeout(function(){
            reject(new Error('Timeout after ' + ms + ' ms')); // (A)
        }, ms)
    });
}

    注意在 A 处超时后rejection并不会取消这个请求,但是会阻止Promise 达到fulfilled状态。
    如下方式使用 timeout(): 

timeout(5000, httpGet("http://example.com/file.txt") )
.then(function(value){
    console.log('contents: ' + value);
})
.catch(function(reason){
    console.log('error or timeout: ' , reason);
});

6. 链式调用 then()

    调用 P.then( onFulfilled, onRejected ) 会得到一个新的Promise Q。这意味着在 Q 中,你可以通过调用 then() 来保持对Promise的控制
    1. Q 在 onFulfilled 或者 onRejected 返回结果的时候,即为resolved。
    2. Q 在 onFulfilled 或者 onRejected 抛出异常的时候,即为rejected。

6.1.  Resolving Q返回一般值

    如果then() 返回的是个一般值,那么可以resolve这个 Promise Q,你可以在下一个 then( ) 取到这个值:

asyncFunc()
.then(function(){
    return 123;
})
.then(function(value){
    console.log(value); // 123
})

6.2. Resolving Q返回then对象(thenables)

    如果then() 返回的是个 then对象R,那么也可以resolve这个Promise Q。then对象(thenable)表示有Promise 风格 then() 方法的任何对象,因此Promises就是then对象。resolve R (例如通过onFulfilled返回)意味着它被插入到 Q 之后:R的settlement将被传递给 Q的onFulfilled 或者 onRejected回调函数。也就是说 Q 转变成了 R。

    

    这个形式主要是用来扁平化嵌套式调用 then(),比如下面的例子:

asyncFunc1()
.then(function(value1){
    asyncFunc2()
    .then(function(value2){
        ...
    });
});

   那么扁平化形式可以变为这样: 

asyncFunc1()
.then(function(value1){
    return asyncFunc2();
})
.then(function(value2){
    ...
});

6.3 Resolving Q from onRejected

   如之前提到的,不管你在错误handler中返回什么,都将成为一个 fulfillment 的值(注意不是rejection 值)。这使得你可以定义失败情况下用到默认值:

retrieveFileName()
.catch(function(){
    // Something went wrong, use a default value
    return 'Untitled.txt';
})
.then(function(fileName){
    ...
});

6.4 抛出异常拒绝Q(Rejecting Q by throwing exceptions)

    从then的任意一个参数抛出的异常被传递给下一个错误handler: 

asyncFunc()
.then(funcrion(value){
    throw new Error();
})
.catch(function(reason){
    // Handle error here
});

6.5 执行过程中的异常(Exceptions in executors)

  executor(new Promise()的回调函数中抛出的异常,将会传递到由executor管理的Promise的错误handler中:

new Promise(function(resolve, reject){
    throw new Error();
})
.catch(function(err){
    // Handle error here
});

6.6 链式的错误处理

    会有一个或多个 then() 调用没有提供错误handler,那么直到出现错误handler该错误才会被传递进去:

asyncFunc1()
.then(asyncFunc2)
.then(asyncFunc3)
.catch(function(reason{
    // something went wrong above
});

7. 组合

    本章节会描述你如何组合现有的Promises 来创建新的Promise。我们已经使用过一种组合Promise 的方式了:通过 then() 连续的链式调用。Promise.all() 和 Promise.race() 提供了另一些组合的形式。

7.1. 通过 Promise.all() 实现 map()

    庆幸的是,基于Promise的函数可以返回结果,因此很多同步工具仍然可以使用。比如你可以使用数组的方法 map():

let fileUrls = [
    'http://example.com/file1.txt',
    'http://example.com/file2.txt'
];
let promisedTexts = fileUrls.map(httpGet);

    promisedTexts 是一个Promises 数组。Promise.all() 可以处理一个Promises 数组(then对象和 其他值可以通过 Promise.resolve() 来转换为 Promises),一旦所有的项状态都为 fulfilled,就会得到取值的数组: 

Promise.all(promisedTexts)
.then(texts=> {
    for (let text of texts) {
        console.log(text);
    }
})
.catch(reason => {
    // Receives first rejection among the Promises
});

7.2. 通过 Promise.race() 实现延时

    Promise.race() 接受一个 Promises 数组(then对象和其他值可以通过 Promise.resolve() 来转换为 Promises),返回一个 Promise 对象 P。第一个传入的 Promise会将其settlement传递给输出的Promise。
    举个例子,使用 Promise.race() 来实现一个 timeout:

Promise.race([
    httpGet('http://example.com/file.txt'),
    delay(5000).then(function(){
        throw new Error('Time out');
    })
])
.then(function(text){ ... })
.catch(function(reason){ ... });

8. Promises总是异步的

    一个Promise 库,不管是同步(正常方式)还是异步(当前代码块之后继续执行直至完成)将结果送给Promise的reactions,都可以完全控制。然而,Promises/A+ 规范约定总是用后一种方式。它以then()方法的需求(2.2.4)来描述:
        只有当执行上下文栈中仅仅包含平台代码时,才可以调用 onFulfilled 或者 onRejected。
    这意味着你可以依赖运行到完成的语态run-to-completion semantics:第一部分中提到的),使得链式的Promises 不会使其他任务没有时间得到处理。

本文转载自:http://blog.chinaunix.net/uid-26672038-id-4900198.html

共有 人打赏支持
一配
粉丝 34
博文 130
码字总数 94295
作品 0
西城
JavaScript 编年小史

1995 由 Netscape 公司雇员Brendan Eich 花不到 10 天时间开发出来。之所以叫 JavaScript,因为 Netscape 想开发一款类 Java 的脚本语言来增强 Web 技术用于和微软竞争。 1996 Netscape 提交...

CPPAlien ⋅ 05/09 ⋅ 0

第一章--JavaScript简介

1. JavaScript的构成 1.1. ECMAScript ECMAScript规定了核心语言的组成部分分别为:语法、类型、语句、关键字、保留字、操作符、对象。 宿主环境:Web浏览器、Node、Adobe Flash。 1.2. DOM...

lovewt ⋅ 06/05 ⋅ 0

python_day14_前端_JS-重写

说明 ECMA(欧洲计算机制造协会)   ECMA(欧洲计算机制造协会)定义了ECMA-262规范.国际标准化组织及国际电工委员会(ISO/IEC)也采纳 ECMAScript 作为标准(ISO/IEC-16262)。从此,Web 浏览...

812374156 ⋅ 05/25 ⋅ 0

融合了 JavaScript 之力的 Nashorn 或被 JDK 11 弃用

还没有使用过 Nashorn ?如果感兴趣的话,建议尽快去尝试一下,因为 Jim Laskey 最近提交了一个新的 JEP 335 草案,准备弃用 Nashorn JavaScript 脚本引擎、API 以及该 jjs 工具。 从 JDK 6 ...

王练 ⋅ 06/08 ⋅ 0

JavaScript 函数深入学习

1 函数 每个函数都是 Function 类型的实例,且都与其他引用类型一样具有属性和方法。由于函数式对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。 1.1 函数概述 1.1....

piada ⋅ 05/15 ⋅ 0

PowJS 更新至 2.2.0,Real-DOM 模板引擎创始者

PowJS 2.2.0 已发布,PowJS 是一个 ECMAScript 6 编译型 Real-DOM 模板引擎. Real DOM 直接在 DOM Tree 上编译, 渲染. DOM Tree 就是模板.原生语法 指令与 ECMAScript 原生语法对应导出视图 ...

喻恒春 ⋅ 05/18 ⋅ 0

30 天精通 RxJS (00): 关于本系列文章

前言 笔者从去年就一直想参加铁人赛 30 天,一方面是希望利用机会把自己的所学做一次整理,另一方面想训练自己组织文章的能力。去年的时候我想写 ECMAScript 2015,也准备了一段时间,结果没...

readilen ⋅ 05/19 ⋅ 0

Chrome V8 与 Node.js

从某种意义上来说,Node.js 并不是一个从零开始编写的 JavaScript 运行时,它其实也是站在“巨人的肩膀”上进行了一系列的拼凑和封装得到的结果。它的高效离不开一些很牛的第三方程序和类库。...

博文视点 ⋅ 06/14 ⋅ 0

前端开发指南:ES6的生成器和迭代器

     ES6为JavaScript语言带来了许多新特性。其中两个特性,生成器和迭代器,极大地改变了我们在更复杂的前端代码中编写特定函数的方式。   虽然他们之间的关系很好,但他们实际上做的...

webstack前端栈 ⋅ 04/19 ⋅ 0

ECMAScript 6 学习笔记(1)--编译环境搭建(VScode)

参考文档:http://es6.ruanyifeng.com/#docs/intro https://www.w3cschool.cn/ecmascript/ ECMAScript是一种定义脚本语言的规范,Javascript正是基于这种规范的一种实现。JavaScript是一种弱...

wx599c47c7bdcad ⋅ 05/18 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Jenkins实践3 之脚本

#!/bin/sh# export PROJ_PATH=项目路径# export TOMCAT_PATH=tomcat路径killTomcat(){pid=`ps -ef | grep tomcat | grep java|awk '{print $2}'`echo "tom...

晨猫 ⋅ 今天 ⋅ 0

Spring Bean的生命周期

前言 Spring Bean 的生命周期在整个 Spring 中占有很重要的位置,掌握这些可以加深对 Spring 的理解。 首先看下生命周期图: 再谈生命周期之前有一点需要先明确: Spring 只帮我们管理单例模...

素雷 ⋅ 今天 ⋅ 0

zblog2.3版本的asp系统是否可以超越卢松松博客的流量[图]

最近访问zblog官网,发现zlbog-asp2.3版本已经进入测试阶段了,虽然正式版还没有发布,想必也不久了。那么作为aps纵横江湖十多年的今天,blog2.2版本应该已经成熟了,为什么还要发布这个2.3...

原创小博客 ⋅ 今天 ⋅ 0

聊聊spring cloud的HystrixCircuitBreakerConfiguration

序 本文主要研究一下spring cloud的HystrixCircuitBreakerConfiguration HystrixCircuitBreakerConfiguration spring-cloud-netflix-core-2.0.0.RELEASE-sources.jar!/org/springframework/......

go4it ⋅ 今天 ⋅ 0

二分查找

二分查找,也称折半查找、二分搜索,是一种在有序数组中查找某一特定元素的搜索算法。搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜素过程结束;如果某一特定元素大于...

人觉非常君 ⋅ 今天 ⋅ 0

VS中使用X64汇编

需要注意的是,在X86项目中,可以使用__asm{}来嵌入汇编代码,但是在X64项目中,再也不能使用__asm{}来编写嵌入式汇编程序了,必须使用专门的.asm汇编文件来编写相应的汇编代码,然后在其它地...

simpower ⋅ 今天 ⋅ 0

ThreadPoolExecutor

ThreadPoolExecutor public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, ......

4rnold ⋅ 昨天 ⋅ 0

Java正无穷大、负无穷大以及NaN

问题来源:用Java代码写了一个计算公式,包含除法和对数和取反,在页面上出现了-infinity,不知道这是什么问题,网上找答案才明白意思是负的无穷大。 思考:为什么会出现这种情况呢?这是哪里...

young_chen ⋅ 昨天 ⋅ 0

前台对中文编码,后台解码

前台:encodeURI(sbzt) 后台:String param = URLDecoder.decode(sbzt,"UTF-8");

west_coast ⋅ 昨天 ⋅ 0

实验楼—MySQL基础课程-挑战3实验报告

按照文档要求创建数据库 sudo sercice mysql startwget http://labfile.oss.aliyuncs.com/courses/9/createdb2.sqlvim /home/shiyanlou/createdb2.sql#查看下数据库代码 代码创建了grade......

zhangjin7 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部