ES6 Promise 浅析

1.  从宏观的角度初步了解Promise

Promise含义不是字面的“誓言”“承诺”之类,而是“先知”的意思。

人类的行为虽然可以并行,但有意识的思维活动却呈现出单线程的特性。

比如,普通人同一时间只能思考一个问题。在思考的过程中,有时会突然受到灵感的启发联想到其他的问题。

熟悉javascript的人也许早已意识到:

人类有意识层面的思维过程和js的执行过程有很多相通之处。

js沿着时间线执行,其间会插入定时器或回调。时间就是这个过程的轴心。

js的创造者当初也许就是为了尽可能简单地和人类思维契合起来,才会如此设计js这个语言。

然而现实世界是复杂的,js需要处理的问题也是复杂的。

在现实世界中,作为一个普通人,思维随着时间这一个维度,始终做着单线程运算。而运算的参数就是“每个当下”所能得到的信息之和。

普通人是相对“惬意”的,如果人类及环境都是“上帝”写的程序,那么这个“上帝”就没有那么“惬意”了。因为,“他”需要预先考虑到每个场景,每个人各种可能的演化。

作为一个程序员,对于所写的程序而言,某种程度上扮演着“上帝”的角色。(看到这里,是不是有些飘飘然的感觉大笑)

但我们并没有超能力,我们的思维依然是单线程的。一不小心就掉进了“回调地狱”中。

这时我们需要一种神器,让我们具有“先知”般的能力Promise就是这类神器的代表。


2. 什么是Promise

Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。

Promise对象有以下两个特点。

(1)对象的状态不受外界影响。 Promise对象代表一个异步操作,有三种状态: Pending(进行中)、 Resolved(已完成,又称Fulfilled)和 Rejected(已拒绝)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

如需要了解更多Promise的规范,请参阅 http://malcolmyu.github.io/malnote/2015/06/12/Promises-A-Plus/


3. ES6 中的Promise

先在控制台打印出Promise看看,console.dir(Promise)



由上图可知,Promise是一个函数,自己身上有all、race、reject、resolve方法,原型上有then、catch等方法

ES6规定,Promise对象是一个构造函数,用来生成Promise实例。

创造了一个Promise实例:

var p = new Promise(function(resolve, reject){
    //做一些异步操作
    setTimeout(function(){
        console.log('执行完成');
        var num = Math.random();
        if(num>=0.5){
            resolve(num);
        } else{
            reject('数字小于0.5');
        }
       
    }, 2000);
});

Promise新建后就会立即执行。

上面的代码中,设置了一个2s后执行的定时器。

2s以后输出“执行完成”,然后生成一个随机数,如果数字大于等于0.5,我们认为是“成功”了,调用resolve修改Promise的状态。否则我们认为是“失败”了,调用reject并传递一个参数,作为“失败”的原因。


resolve函数的作用是,将Promise对象的状态从“未完成”变为“已解决”(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;

reject函数的作用是,将Promise对象的状态从“未完成”变为“拒绝”(即从Pending变为Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。


4. Promise的使用实例

下面是异步加载图片的例子。

function loadImageAsync(url) {
  return new Promise(function(resolve, reject) {
    var image = new Image();

    image.onload = function() {
      resolve(image);
    };

    image.onerror = function() {
      reject(new Error('Could not load image at ' + url));
    };

    image.src = url;
  });
}

上面代码中,使用Promise包装了一个图片加载的异步操作。如果加载成功,就调用resolve方法,否则就调用reject方法。


Promise.prototype.then()

then方法的第一个参数是Resolved状态的回调函数,第二个参数(可选)是Rejected状态的回调函数。

从表面上看,Promise只是简化了层层回调的写法,而实质上,Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递callback函数要简单、灵活的多。

function runAsync1(){
    var p = new Promise(function(resolve, reject){
        //做一些异步操作
        setTimeout(function(){
            console.log('执行完成1');
            resolve('数据1');
        }, 2000);
    });
    return p;            
}

function runAsync2(){
    var p = new Promise(function(resolve, reject){
        setTimeout(function(){
            console.log('执行完成2');
            resolve('数据2');
        }, 2000);
    });
    return p;            
}

function runAsync3(){
    var p = new Promise(function(resolve, reject){
        setTimeout(function(){
            console.log('执行完成3');
            resolve('数据3');
        }, 2000);
    });
    return p;            
}

链式调用代码如下:

runAsync1()
.then(function(data){
    console.log(data);
    return runAsync2();
})
.then(function(data){
    console.log(data);
    return runAsync3();
})
.then(function(data){
    console.log(data);
});
以上代码的输出为:

执行完成1

数据1

执行完成2

数据2

执行完成3

数据3


Promise.prototype.catch()

Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。

var promise = new Promise(function(resolve, reject) {
  throw new Error('test');
});
promise.catch(function(error) {
  console.log(error);
});
上面代码中,promise抛出一个错误,就被catch方法指定的回调函数捕获。

需要注意的是,catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法。

var someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行会报错,因为x没有声明
    resolve(x + 2);
  });
};

someAsyncThing()
.catch(function(error) {
  console.log('oh no', error);
})
.then(function() {
  console.log('carry on');
});
// oh no [ReferenceError: x is not defined]
// carry on
上面代码运行完 catch方法指定的回调函数,会接着运行后面那个 then方法指定的回调函数。如果没有报错,则会跳过 catch方法。


Promise.all()

Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。

下面是一个例子,其中 runAsync1、 runAsync1 和runAsync3上面的例子中已有定义

Promise
.all([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
    console.log(results);
});
以上代码的输出为:

执行完成1

执行完成2

执行完成3

["数据1"," 数据2","数据3"]


有了all,你就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据。


Promise.race()

Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。

all方法的效果实际上是「谁跑的慢,以谁为准执行回调」,那么相对的就有另一个方法race「谁跑的快,以谁为准执行回调」。

我们把上面runAsync1的延时改为1秒来看一下:

Promise
.race([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
    console.log(results);
});
以上代码的输出为:

执行完成1

数据1

执行完成2

执行完成3

在then里面的回调开始执行时,runAsync2()和runAsync3()并没有停止,仍旧再执行。于是再过1秒后,输出了他们结束的标志。

这个race有什么用呢?使用场景还是很多的,比如我们可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作,代码如下:

//请求某个图片资源
function requestImg(){
    var p = new Promise(function(resolve, reject){
        var img = new Image();
        img.onload = function(){
            resolve(img);
        }
        img.src = 'xxxxxx';
    });
    return p;
}

//延时函数,用于给请求计时
function timeout(){
    var p = new Promise(function(resolve, reject){
        setTimeout(function(){
            reject('图片请求超时');
        }, 5000);
    });
    return p;
}

Promise
.race([requestImg(), timeout()])
.then(function(results){
    console.log(results);
})
.catch(function(reason){
    console.log(reason);
});
requestImg函数会异步请求一张图片,我把地址写为"xxxxxx",所以肯定是无法成功请求到的。timeout函数是一个延时5秒的异步操作。我们把这两个返回Promise对象的函数放进race,于是他俩就会赛跑,如果5秒之内图片请求成功了,那么遍进入then方法,执行正常的流程。如果5秒钟图片还未成功返回,那么timeout就跑赢了,则进入catch,报出“图片请求超时”的信息。

补充

以上代码例子均来源于网络。

主要参考的文章:

http://malcolmyu.github.io/malnote/2015/06/12/Promises-A-Plus/

http://www.cnblogs.com/lvdabao/p/es6-promise-1.html

http://www.zhangxinxu.com/wordpress/2014/02/es6-javascript-promise-%E6%84%9F%E6%80%A7%E8%AE%A4%E7%9F%A5/

http://es6.ruanyifeng.com/#docs/promise

https://segmentfault.com/a/1190000000684654


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值