ccforward / cc

Code & Blog
1.59k stars 193 forks source link

34.ES6 笔记三 - Promise #35

Open ccforward opened 8 years ago

ccforward commented 8 years ago

ES6 笔记三 - Promise

一个稍微复杂点的例子_https://github.com/ccforward/cc/blob/master/promise/example-new.js_

_ES6规范确认以前,写过一个Promise的简单实现_

Promise就是一个容器,里面保存着某个未来才会结束的事件(一个异步操作)的结果。作为一个对象,从Promise可以获取异步操作的消息。

两个特点

  1. 对象的状态不受外界影响。
    Promise对象代表一个异步操作,有三种状态:Pending(进行中) Resolved(已完成 Fullfilled) Rejected(已失败)。
    只有异步操作的结果,可以决定当前是哪一种状态,任何操作都无法改变这个状态。
  2. 一旦状态改变,就不会再变,任何时候都可以得到这个结果。
    Promise的状态改变只有两种可能:Pending -> Resolved 或者 Pending -> Rejected。
    只要两种情况发生,状态就固定不会改变了,会一直保持这个结果。

通过Promise对象就可以将异步操作以同步操作的流程表达出来,避免嵌套层层回调的地狱。

缺点

var getJSON = function(url){
    var promise = new Promise(function(){
        var xhr = new XMLHttpRequest();
        xhr.open('GET', url);
        xhr.onreadystatechange = hanlder;
        xhr.responseType = 'json';
        xhr.setRequestHeader('Accept', 'application/json');
        xhr.send();

        function hanlder(){
            if(this.readyState !== 4){
                return;
            }
            if(this.status == 200){
                resolve(this.response);
            } else {
                reject(new Error(this.statusText));
        }
    });

    return promise;
}

如果调用 resolve 或者 reject 函数时带有参数,那么这些参数会传递给回调函数。 reject的参数通常是 Error 对象的实例,表示跑出的错误。

Promise.prototype.then

then 方法是为 Promise 实例添加状态改变时的回调函数。then方法的第一个参数是 Resolved 状态的回调函数,第二个参数(可选)是 Rejected 状态的回调函数。

then方法返回的是一个新的Promise实例(不是原来那个),可采用链式写法。如下:

getJSON("a.json").then(function(res){
    return getJSON(res.dataURL)
}).then(function fnA(data){
        console.log('Resolved: ' + data);
    }, function fnB(err){
        console.log('Rejected: ' + err);
});

第一个then方法指定的回调函数返回的是一个新的 Promise 对象。这时,第二个then方法指定的回调函数就等待这个新的 Promise 对象发生变化来调用A或B函数

上面的代码改为箭头函数更简洁:

getJSON("a.json").then(
    res => getJSON(res.dataURL)
).then(
    data => console.log('Resolved: ' + data),
    err => console.log('Rejected: ' + err)
);

Promise.prototype.catch

Promise.prototype.catch 是 .then(null, rejection) 的别名。

如果异步操作抛出错误,状态就会变为 Rejected, 就会调用catch方法指定的回调函数处理这个错误。

一般来说,不要再then方法中定义 Rejected 状态的回调函数(then的第二个参数),而应该总是使用 catch 方法。

//bad
promise
    .then(function(data){
        // success
    },function(err){
        // error
    })

// good
promise
    .then(function(data){ //回调函数
        // success
    })
    .catch(function(err){
        // error
    })

跟传统的 try/catch 不同,如果没有使用catch方法指定错误处理的回调函数, Promise对象抛出的错误不会传递到外层代码,即不会有任何反应。

catch 返回的还是一个 Promise 对象,因此后面可以继续调用 then 方法。

var someAsync = function(){
    return new Promise(function(resolve, reject){
        // x没声明 报错 
        resolve(x+2);
    });
}

comAsync()
.catch(function(err){
    console.log('error: ' + err)
})
.then(function(){
    console.log('go on')
});

// error: [ReferenceError: x is not defined]
// go on  接着运行then方法

Promise.all()

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

var p = Promise.all([p1, p2, p3]);

p的状态由 p1 p2 p3 决定

  1. 只有 p1 p2 p3 都为 Fullfilled p的状态才会是 Fullfilled。p1 p2 p3的返回值组成一个数组传递给p的回调函数。
  2. 如果 p1 p2 p3 有一个为 Rejected 则 p 的状态为 Rejected。此时第一个被 Rejected 的实例的返回值会传递给p的回调函数。

    Promise.race()

Promise.race 也是将多个 Promise 实例包装成一个新的 Promise 实例。

var p = Promise.race([p1, p2, p3]);

只要 p1 p2 p3 中有一个实例率先改变状态,p的状态就跟着改变

var p = Promise.race([
  fetch('/data.json'),
  new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('request timeout')), 5000)
  })
])
p.then(response => console.log(response))
p.catch(error => console.log(error))

如果5秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数。

Promise.resolve()

Promise.resolve方法将现有对象转为Promise对象。

Promise.resolve($.ajax('/whatever.json'));

把 jQuery 生成的 deferred 对象转为新的 Promise 对象

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))

如果resolve方法的参数是一个不具有then方法的对象,则Promise.resolve方法返回一个新的Promise对象,状态为Resolved。

var p = Promise.resolve('Hello');

p.then(function (s){
  console.log(s)
});

字符串Hello不属于异步操作(判断方法是它不是具有then方法的对象),返回Promise实例的状态从一生成就是Resolved,所以回调函数会立即执行。

Promise.resolve方法允许调用时不带参数,直接调用Promise.resolve可以获得一个 Promise 对象。

var p = Promise.resolve();

p.then(function () {
  // ...
});

Promise.reject()

Promise.reject() 方法也会返回一个新的Promise实例,且状态为rejected。

它的参数用法与Promise.resolve方法完全一致。

var p = Promise.reject('出错了');
// 等同于
var p = new Promise((resolve, reject) => reject('出错了'))

p.then(null, function (s){
  console.log(s)
});
// 出错了

附加方法

Promise.prototype.done = function (onFulfilled, onRejected) {
  this.then(onFulfilled, onRejected)
    .catch(function (reason) {
      // 抛出一个全局错误
      setTimeout(() => { throw reason }, 0);
    });
};
Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};