资讯专栏INFORMATION COLUMN

ECMAScript6(15):Promise 对象

edgardeng / 2778人阅读

摘要:建立对象用来传递异步操作消息,代表一个未来才会知道结果的事件,并且对不同事件提供统一的以便进一步处理。此时第一个被的的返回值作为新的对象的回调函数参数伪代码由于没有正确的由于没有正确的将多个对象合并成一个新的实例。

Promise 建立

Promise 对象用来传递异步操作消息,代表一个未来才会知道结果的事件,并且对不同事件提供统一的 API 以便进一步处理。Promise 具有以下特点:

由异步操作结果决定改状态,其他操作绝不影响该状态;

对象状态不受外界影响:Promise 代表的异步操作有三个状态:

Pending: 进行中

Resolved: 已完成(Fulfilled)

Rejected: 已失败

一旦状态改变,就不会再变:Promise 的状态只有2种可能:

从 Pending 到 Resolved

从 Pending 到 Rejected

对于同一个 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 传出的值作为其唯一的参数。

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

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 异步加载图片:

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 = "
" + json + "
", 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);   //不执行
});

这里需要注意以下几点:

reject 和 throw 一样可以抛出错误。

在 Promise 状态变为 resolved 或 rejected 之后抛出的错误会被忽略。

建议总是使用 catch() 方法,而不要在 then() 方法中定义 reject 函数。

如果一个 promise 既没有 catch方法,也没有可以捕获到错误的 then 方法,那么这个错误就消失了。它不会到 promise 外面来。

try...catch... 只能捕获同步代码的错误,不能捕获异步代码的错误(这个是 ES5 就有的)。

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种情况:

当所有构成它的 promise 对象的状态都变成 resolved,这个新的对象状态才变为 resolved。此时构成它所有的 Promise 的返回值构成一个数组作为新的 promise 对象的回调函数参数;

当所有构成它的 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

异步中模拟 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);
})();

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/97442.html

相关文章

  • ECMAScript6(16):异步编程

    摘要:异步编程程序执行分为同步和异步,如果程序每执行一步都需要等待上一步完成才能开始,此所谓同步。因此异步编程十分重要。 异步编程 程序执行分为同步和异步,如果程序每执行一步都需要等待上一步完成才能开始,此所谓同步。如果程序在执行一段代码的同时可以去执行另一段代码,等到这段代码执行完毕再吧结果交给另一段代码,此所谓异步。比如我们需要请求一个网络资源,由于网速比较慢,同步编程就意味着用户必须等...

    曹金海 评论0 收藏0
  • ECMAScript6(0):ES6简明参考手册

    摘要:允许我们把水平的代码回调函数的地狱转换为竖直的代码在之前,我们使用或是,现在我们有了这里我们有个,执行成功时调用的函数和失败时调用的函数。使用的好处使用嵌套的回调函数处理错误会很混乱。 es6-参考手册 该手册包括ES2015[ES6]的知识点、技巧、建议和每天工作用的代码段例子。欢迎补充和建议。 var 和 let / const 除了var,我们现在有了两种新的标示符用来存储值——...

    ARGUS 评论0 收藏0
  • ECMAScript6(10):Symbol基本类型

    摘要:基本类型是一种解决命名冲突的工具。这样,就有了个基本类型和个复杂类型使用需要注意以下几点和一样不具有构造函数,不能用调用。判断对象是否某个构造函数的实例,运算符会调用它是一个数组对象属性。即,当存在时,以此为构造函数构建对象。 Symbol基本类型 Symbol 是一种解决命名冲突的工具。试想我们以前定义一个对象方法的时候总是要检查是否已存在同名变量: if(String && Str...

    lavor 评论0 收藏0
  • ECMAScript6(14):iterator 迭代器

    摘要:由于中引入了许多数据结构算上原有的包括等等数组需要一个东西来管理他们这就是遍历器。数组默认遍历器遍历值相当于依次输出依次输出依次输出依次输出不难看出默认得到值而只能得到索引。即遍历器的本质就是一个指针。 由于 ES6 中引入了许多数据结构, 算上原有的包括Object, Array, TypedArray, DataView, buffer, Map, WeakMap, Set, We...

    toddmark 评论0 收藏0
  • Javascript的异步编程:Promise

    摘要:对象是工作组为异步编程提供的统一接口,是中提供了对的原生支持,就是在未来发生的事情,使用可以避免回调函数的层层嵌套,还提供了规范更加容易的对异步操作进行控制。是执行完之后的回调,可以用方法分别指定和的回调。 Promise对象是CommonJS工作组为异步编程提供的统一接口,是ECMAScript6中提供了对Promise的原生支持,Promise就是在未来发生的事情,使用Promis...

    forsigner 评论0 收藏0

发表评论

0条评论

edgardeng

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<