ECMAScript6(15):Promise 对象

Promise 创建

Promise 对象用来传递异步操做消息,表明一个将来才会知道结果的事件,而且对不一样事件提供统一的 API 以便进一步处理。Promise 具备如下特色:ajax

  • 由异步操做结果决定改状态,其余操做毫不影响该状态;
  • 对象状态不受外界影响:Promise 表明的异步操做有三个状态:json

    1. Pending: 进行中
    2. Resolved: 已完成(Fulfilled)
    3. Rejected: 已失败
  • 一旦状态改变,就不会再变:Promise 的状态只有2种可能:数组

    1. 从 Pending 到 Resolved
    2. 从 Pending 到 Rejected

对于同一个 promise, 当以上状态发生一个(只能发生其一),就不会再改变了。以后任什么时候间你都能获得这个状态,且永不改变。
有了 Promise 就能够将层层的回调写为同步的样子,表示起来更清晰。不过须要注意如下几点:promise

  • Promise 一旦创建就当即执行,而且没法中断或取消
  • 若是没有设置回调函数,那么 Promise 中的产生的错误不会抛到外部
  • Pending 状态时,咱们没法知道其具体进度

Promise 的基本结构以下:浏览器

var promise = new Promise(function(resolve, reject){
  if(/*异步操做成功*/){
    resolve(value);
  } else {
    reject(error);
  }
});

构造函数接受一个回调函数为参数,回调函数具备2个参数,也都是函数,resolve 在 Promise 状态变为 resolved 时调用,reject 在 Promise 状态变为 rejected 时调用。resolve 的接受一个参数——值或另外一个 promise 对象; rejectj接受一个参数——错误。须要说明的是,这里的 resole 和 reject 函数已经由系统部署好了,咱们能够不写。服务器

promise 构建好之后咱们就能够调用它的then()方法,then(resolve(value){},reject(value){})方法接受2个函数参数,resolve 在 Promise 状态变为 resolved 时调用,reject 在 Promise 状态变为 rejected 时调用。其中 reject 参数是可选的。和构造函数不一样的是,then 方法的 reject 和 resolve 都使用 promise 传出的值做为其惟一的参数。app

这里写一个简单的例子,理解一下:异步

function timeout(ms){
  return new Promise((resolve, reject) => {
    console.log("promise");            //"promise"
    setTimeout(resolve, ms, 'done');
  });
}
timeout(2000).then((value) => {
  console.log(value);                  //2秒后获得 "done"
});

利用 Promise 异步加载图片:async

function loadImageAsync(url){
  return new Promise(function(resole, 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 实现 Ajax:函数

var id = document.getElementById("primary");
  var getJSON = function(url){
    var promise = new Promise(function(resolve, reject){
      var client = new XMLHttpRequest();
      client.open("GET", url);
      client.onreadystatechange = handler;
      client.response = "json";
      client.setRequestHeader("Accept", "application/json");
      client.send();

      function handler(){
        if(client.readyState !== 4) return;
        if(this.status === 200){
          resolve(client.response);
        } else {
          reject(new Error(this.statusText));
        }
      }
    });
    return promise;
  }
  getJSON('info.json').then(
    json => id.innerHTML = "<pre>" + json + "</pre>",
    err => id.innerHTML = err
  );

若是 resolve 的参数是一个promise:

var p1 = new Promise(function(resolve, reject){
  //...
});
var p2 = new Promise(function(resolve, reject){
  //...
  resolve(p1);
});

上面代码中 p1 的状态传给了 p2,也就是p1运行完成(状态为 resolve 或 reject)后 p2 的回调函数会马上开始执行:

var p1 = new Promise(function(resolve, reject){
  setTimeout(() => reject(new Error('failed')), 3000);
});
var p2 = new Promise(function(resolve, reject){
  setTimeout(() => resolve(p1), 1000);
});
p2.then(result => console.log(result));
p2.catch(error => console.log(error));

p1 创建,进入 setTimeout 异步计时器。以后 p2 创建,进入 setTimeout 异步计时器。1s 后 p2 准备执行 resolve, 可是 resolve 的参数是 p1, 此时 p1 仍是 Pending 状态,因此 p2 开始等待。又过了 2s, p1 的 reject 执行,变为 rejected 状态,随即 p2 也跟着变成 rejected 状态。

Promise 对象方法

  • then() 方法

then(resolve(value){},reject(value){})方法接受2个函数参数,resolve 在 Promise 状态变为 resolved 时调用,reject 在 Promise 状态变为 rejected 时调用。其中 reject 参数是可选的。和构造函数不一样的是,then 方法的 reject 和 resolve 都使用 promise 传出的值做为其惟一的参数。
then() 方法返回一个新的 Promise 实例,注意,不是以前那个。所以能够用链式调用,不断添加"回调"函数。 then 的返回值成了下一个 then 中回调函数的参数:

var p = new Promise(function(resolve, reject){
  resolve("from new Promise");
}).then(function (value){
  console.log(value);     //from new Promise    其次输出这个
  return "from the first 'then'";
}).then(function(value){
  console.log(value);     //from the first 'then'    最后输出这个
  return "from the second 'then'";
});
console.log(p);           //Promise{...}    先输出这个

注意,若是 promise 的状态是 resolved 则执行 then参数中的第一个回调函数;若是 promise 的状态是 rejected 则执行 then参数中的第二个回调函数。这个状态是不断传递下来的,这一点和以前的例子相似。

  • catch() 方法:

catch(reject) 方法是 then(null, reject) 的别名,在发生错误的时候执行其参数函数:

new Promise(function(resolve, reject){
  resolve("resolved");
}).then(function(val){
  console.log(val);           //resolved
  throw new Error("man-made Error");
}).catch(function(err){
  console.log(err.message);   //man-made Error
});

错误会从最初的请求沿着回调函数,一直被传递下来。这一点和传统的错误冒泡相似,不管哪里有错误均可以被捕获到:

new Promise(function(resolve, reject){
  reject(new Error("original Error"));
}).then(function(val){
  console.log(val);           //不执行
  throw new Error("man-made Error");
}).catch(function(err){
  console.log(err.message);   //original Error
});

固然也能够在半路截住错误:

new Promise(function(resolve, reject){
  reject(new Error("original Error"));
}).then(function(val){
  console.log(val);           //不执行
  throw new Error("man-made Error");
}, function(err){
  console.log(`Uncaught Error: ${err.message}`);  //Uncaught Error: original Error
}).catch(function(err){
  console.log(err.message);   //不执行
});

这里须要注意如下几点:

  1. reject 和 throw 同样能够抛出错误。
  2. 在 Promise 状态变为 resolved 或 rejected 以后抛出的错误会被忽略。
  3. 建议老是使用 catch() 方法,而不要在 then() 方法中定义 reject 函数。
  4. 若是一个 promise 既没有 catch方法,也没有能够捕获到错误的 then 方法,那么这个错误就消失了。它不会到 promise 外面来。
  5. try...catch... 只能捕获同步代码的错误,不能捕获异步代码的错误(这个是 ES5 就有的)。
  6. catch() 方法能够继续抛出错误,就像 try...catch 中的 catch 同样能够抛出错误。

这里须要说明的是第4条:错误不会到 Promise 外面是 ES6 规范的说法。具体理解(浏览器环境):控制台依旧会报错,可是不影响 promise 语句以后续代码执行。此外,promise 语句内的异步语句(如事件,定时器等等)抛出的错误,不属于 promise 内部,发生错误会传播出去:

var p = new Promise(function(resolve, reject){
  resolve("ok");
  setTimeout(function(){throw new Error("setTimeout error")},0);
});
p.then(function(val){console.log(val);});     //ok
//Uncaught Error: setTimeout error

其次,就以上前两个注意事项举一例说明:

new Promise(function(resolve, reject){
  resolve("resolved");
  throw "original Error";     //被忽略
}).then(function(val){
  console.log(val);           //resolved
  throw (new Error("man-made Error"));
}).catch(function(err){
  console.log(err.message);   //man-made Error
});

catch 方法的返回值仍是一个新的 promise 对象,能够继续调用 then 等其余方法:

new Promise(function(resolve, reject){
  reject(new Error("reject"));
}).catch(function(err){
  console.log("1st catch");   //被跳过
  return "continue";
}).then(function(val){
  console.log(val);           //continue
});

若是 catch以前没有错误,该 catch 会被跳过。这意味着,catch 不能捕获在其后面的语句中出现的错误:

new Promise(function(resolve, reject){
  resolve("resolved");
}).catch(function(err){
  console.log("1st catch");   //被跳过
}).then(function(val){
  console.log(val);           //resolved
  throw (new Error());
}).catch(function(err){
  console.log("2nd catch");   //2nd catch
});
  • finally() 方法

finally() 接受一个回调函数(无参数)为参数,和 try...catch...finally 中的 finally 相似,不论 promise 是什么状态,该回调函数都必定会运行。能够用它关闭文件,或者关闭服务器等:

server.listen(0).then(function(){
  //do sth.
}).finally(server.stop);

finally() 内部实现以下:

Promise.prototype.finally = function(callback){
  return this.then(
    value => {Promise.resolve(callback()).then(() => value)},
    error => {Promise.resolve(callback()).then(() => {throw error})}
  );
};
  • done() 方法

done() 方法用在 promise 处理语句的末端,用来处理可能未捕获的错误,并抛向全局。若是其带有参数,能够等效为 done() 以前多了一个 then():

p.done(fun1, fun2);
//至关于
p.then(fun1,fun2).done();

done() 内部实现以下:

Promise.prototype.done = function(onResolve, onRejected){
  this.then(onResolve, onRejected).catch(function(err){
    setTimeout(() => {throw err}, 0);
  });
};

Promise 静态方法

  • Promise.all()

将多个 promise 对象合并成一个新的 promise 实例。其接受一个装仅有 promise 对象的可遍历结构为参数,若是不是 promise 对象,系统会调用 Promise.resolve() 进行类型转换。
promise.all() 方法获得的新的 promise 对象状态由构成它的全部 promise 对象决定,具体分为2种状况:

  1. 当全部构成它的 promise 对象的状态都变成 resolved,这个新的对象状态才变为 resolved。此时构成它全部的 Promise 的返回值构成一个数组做为新的 promise 对象的回调函数参数;
  2. 当全部构成它的 promise 对象的状态有一个变成 rejected,这个新的对象状态就变为 rejected。此时第一个被 reject 的 Promise 的返回值做为新的 promise 对象的回调函数参数;
//伪代码, 因为没有正确的 url
var getJSON = function(url){
  var promise = new Promise(function(resolve, reject){
    var client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.response = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();
    function handler(){
      if(client.readyState !== 4) return;
      if(this.status === 200){
        resolve(client.response);
      } else {
        reject(new Error(this.statusText));
      }
    }
  });
  return promise;
}
var pros = ['url1', 'url2', 'url3'].map(url => getJSON(url));
Promise.all(pros).then(function(){
  console.log("all successful");
}, function(){
  console.log("one rejected");       //one rejected, 因为没有正确的 url
});
  • Promise.race()

将多个 promise 对象合并成一个新的 promise 实例。其接受一个装仅有 promise 对象的可遍历结构为参数,若是不是 promise 对象,系统会调用 Promise.resolve() 进行类型转换。
和 promise.all() 不一样的是 Promise.race() 方法获得的新的 promise 对象状态由构成它的 promise 对象中最早改变状态的那一个决定。

//伪代码, 因为没有正确的 url
var getJSON = function(url){
  var promise = new Promise(function(resolve, reject){
    var client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.response = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();
    function handler(){
      if(client.readyState !== 4) return;
      if(this.status === 200){
        resolve(client.response);
      } else {
        reject(new Error(this.statusText));
      }
    }
  });
  return promise;
}
//若是5s不能得到数据就报错
var p = Promise.race([
  getJSON("url"),
  new Promise(function(resolve, reject){
    setTimeout(() => reject(new Error("Timeout")), 5000);
  })
]).then(res => console.log(res))
.catch(err => console.log(err));    //Error, 因为没有正确的 url
  • Promise.resolve()

将现有对象转化为 promise 对象:

var p = Promise.resolve($.ajax('url'));  //jQuery的 $.ajax 方法
//等同于:
var p = new Promise(function(resolve){
  resolve($.ajax('url'));
});

若是传入 Promise.resolve() 的对象不具备 then 方法(ie. unthenable), 则返回一个状态为 resolved 的新 promise 对象。

Promise.resolve("hello").then(function(val){
  console.log(val);                             //hello
});

若是你仅仅想获得一个 promise 对象,那利用 resolve() 方法是最简单的:

var promise = Promise.resolve();
  • Promise.reject()

Promise.reject(reason), 返回一个状态为 rejected 的 promise 实例。参数 reason 会被传递被实例的回调函数。

Promise.reject(new Error("error occured")).catch(err => console.log(err.message));  //error occured

应用举例

  • 加载图片:
var preloadImage = function(url){
  return new Promise(function(resolve, reject){
    var image = new Image();
    image.onload = resolve;
    image.onerror = reject;
    image.src = url;
  });
};
  • 使用 Generator 管理流程,用 promise 进行异步操做
function getFoo(){
  return new Promise(function(resolve){
    resolve("foo");
  });
}
function* gen(){
  try{
    var foo = yield getFoo();
    console.log(foo);
  } catch(e) {
    console.log(e);
  }
}

var it = gen();
(function go(result){
  if(result.done) return result.value;
  return result.value.then(function(value){
    return go(it.next(value));
  }, function(err){
    return go(it.throw(error));
  });
})(it.next());      //foo
  1. 异步中模拟 sleep 函数
const sleep = (time) => new Promise(function(resolve){
  setTimeout(resolve, time);
});
(async () => {
  for(var i = 0; i < 5; i++){
    await sleep(1000);
    console.log(new Date, i);
  }
  await sleep(1000);
  console.log(new Date, i);
})();
相关文章
相关标签/搜索