资讯专栏INFORMATION COLUMN

es6 - Promise

wemallshop / 2528人阅读

摘要:所谓异步编程中的异步是相对于同步的概念的。是一系列异步编程规范的统称。如果中的回调函数返回一个值,那么返回的将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。参考介绍基础篇深入理解与异步编程。

es6 promise与异步编程

对于一些还不具备大量编程经验的朋友来说,promise可能是es6比较难以掌握的点。首先是很多名词,比如Promises,es6 Promise, 回调函数(callback),Promise/A+,异步编程等。下面就首先介绍下这些名词的含义和区别。

所谓异步编程中的异步是相对于同步的概念的。js是单线程的语言,同一时间只能做一件事,为了指定一些稍后要执行的代码,我们需要异步。在客户端,主要的异步方式有事件,setTimeout,Ajax等。Node的发展大大扩展了js语言的边界,我们知道,Node使用非阻塞IO模型,它使用回调函数模式来实现异步编程。比如:

readFile("example.txt", function(err, contents){
    if(err){ throw err; }
    console.log(contents);
});
console.log("Hi!");

上面代码中readFile的第二个参数就是回调函数。它会在读取完example.txt后被添加到执行队列中。上面代码的执行顺序是--执行readFile函数,在遇到读取文件时暂停,打印"Hi",读取文件结束后将回调添加到作业队列中,执行回调函数,打印contents。

本来呢,使用回调函数是能够完成异步编程的。但是随着代码的逻辑越复杂,这种异步编程方式越来越难以阅读和追踪程序错误,所以发展出了Promises规范来完成异步编程。

Promises是一系列异步编程规范的统称。我们需要了解的是其中的Promise/A+规范。es6通过Promise这个内建对象实现了该规范。所以我们可以使用es6中的Promise对象来进行异步编程。

下面将对es6中的Promise对象进行介绍。至于jQuery中延迟对象$.deferred(),根据规范自己实现promise和ES7的Async/Await异步方式等更多内容,后面会专门写一篇文章进行介绍。

语法 Promise的3种状态

一个promise实例有3种状态,分别是:

pending -- 挂起,表示Promise结果还未知。

fulfilled -- 已完成, 表示Promise成功完成。

rejected -- 已拒绝,表示Promise未成功结束。

promise处于这3种状态中的一种,并且可以由pending状态变为fulfilled状态,或由pending变为rejected状态。反之则不行。

为了便于理解,下面将通过一个生活化的例子,来解释什么是Promise?

Promise是允诺的意思。它就是一个关于未发生的事情的承诺。比如:

你订了一份烧烤,店家说半个小时内送到,这就是一个Promise。现在,这个Promise还没有发生,所以可能半个小时内配送成功或者失败。对此,你预备了两种处理方式:成功 -- 美滋滋的吃烧烤,失败 -- 去楼下店里吃。

在半个小时内,这个Promise处于pending状态,你正常上网,撸代码。一段时间后,这个promise就有了结果。是成功(fulfilled)或者失败(rejected)。根据这个结果,你之前的两种处理方式就会相应执行。这就是promise。

对应的代码如下:

let promise = new Promise(function(resolve, reject){
    //等待店家送来中...
    let result = "配送成功"? true : false;
    if(result){
        resolve(value);
    }else{
        reject(reason);
    }
});

promise.then(function(value){
    //美滋滋吃烧烤...
    //value为上面resolve()中传递的值, 比如共100块钱。
}, function(reason){
    //叫上隔壁老王去楼下吃...
    //reason为上面reject()的传递的原因,比如烤糊了...
});

上面代码就是通过promise异步编程的代码。这里要注意的是Promise构造函数接收一个函数作为参数,函数内部是异步的逻辑。这个函数接收两个参数:resolve和reject。resolve()可以把promise推向fulfilled状态,reject()可以把promise推向rejected状态。

promise有个then方法,用于处理promise成功或失败后的逻辑。then有两个参数:
参数1为promise成功时执行的函数,该函数的参数value对应于上面resolve(value)中的value值;
参数2为promise失败时执行的函数,该函数的参数reason对应于reject(reason)中的reason值,表示失败的原因。
一旦promise有了结果(成功或失败),就会执行对应then中的函数。

通过Promise处理Ajax的例子

Ajax是客户端最常用的异步编程场景,下面一个例子演示了使用Promise进行Ajax操作的代码。

function getData(method, url){
  let promise = new Promise(function(resolve, reject){
    let xmlHttp = new XMLHttpRequest();
    xmlHttp.open(method, url);
    xmlHttp.send();
    xmlHttp.onload = function () {
      if (this.status == 200 ) {
        resolve(this.response);
      } else {
        reject(this.statusText);
      }
    };
    xmlHttp.onerror = function () {
      reject(this.statusText);
    };
  })
  return promise;
}

getData("get","www.xxx.com").then(successFun, failFun);

function successFun(value){
  //Ajax成功处理函数...
}
function failFun(reason){
  //Ajax失败处理函数...
}
创建一个已决的Promise

前面的例子promise创建时,promise都处于pending状态,根据异步操作的结果将promise推向成功或失败状态。

Promise类型有两个静态方法Promise.resolve(value),Promise.reject(reason)可以分别创建已经是fulfilled和已经是rejected状态的promise。
比如:

let promise = Promise.resolve(44);
promise.then(function(value){
  console.log("fulfilled", value);
})

上面代码promise在被创建出来时,已经是fulfilled状态,接下来会直接将then中的回调函数加入到作业队列中,等待作业队列中前面的任务完成后执行该函数。

这里传入Promise.resolve(value)和Promise.reject(reason)中的参数和之前Promise构造是对应的参数是一样的。

Promise.prototype.then()和Promise.prototype.catch()

上面已经演示过promise实例上then方法的用法,每一个promise实例还具有catch方法。

catch()方法只处理reject的情况,他的行为与调用Promise.prototype.then(undefined, onRejected)相同。比如:

let p = new Promise(function(resolve, reject){
    //...
    reject(new Error("something wrong!"))
})
p.catch(function(reason){
    //拒绝
})

上面catch方法中的回调在promise被reject时调用。

then()和catch()的返回值

每次对then()或catch()的调用都会返回另一个promise,这也是很多代码可以写成类似链式调用的原因。比如:

let p1 = new Promise(function(resolve, reject){
  resolve(42);
});
let p2 = p1.then(function(value){
  console.log(value);
})

p2.then(function(){
  console.log("Finished");
}, function(){
    console.log("something wrong!");
});

p1 == p2 // false,注意:p1.then()会返回一个新的promise,所以p1与p2并不相等

//可以写成链式调用的形式,比如
p1.then(function(value){
  console.log(value)
}).then(function(){
  console.log("do something");
}).then(function(){
  console.log("Finished");
})

在上面代码中,p1.then()返回了一个promise为p2, 那么p2的状态和p1之间有什么关系呢?

更具体一点说,当p1变为fulfilled时,p1.then()返回的p2是什么状态呢?二者有什么联系呢?

p2的行为与p1.then()中回调函数的返回值有关:

如果then中的回调函数抛出一个错误,或者回调函数中调用reject(reason),那么then返回的Promise将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。

如果then中的回调函数返回一个值,那么then返回的Promise将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。

如果then中的回调函数返回一个已经是接受状态的Promise,那么then返回的Promise也会成为接受状态,并且将那个Promise的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。

如果then中的回调函数返回一个已经是拒绝状态的Promise,那么then返回的Promise也会成为拒绝状态,并且将那个Promise的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。

如果then中的回调函数返回一个未定状态(pending)的Promise,那么then返回Promise的状态也是未定的,并且它的终态与那个Promise的终态相同;同时,它变为终态时调用的回调函数参数与那个Promise变为终态时的回调函数的参数是相同的。

如果then中的回调函数无显式的返回值,并且也没有调用reject(),那么返回的Promise为接收状态。

Promise.all()处理多个promise

Promise内建对象上的静态方法Promise.all()用于处理多个promise的情况。

Promise.all([promise1, promise2,...])返回一个promise的实例,接收一个promise组成的数组为参数。只有当数组内的promise都成功时,才会调用对应的then中的成功处理函数,只要有一个不成功,那么调用对应的拒绝处理函数。

依然使用前面那么订烧烤的例子,你不仅订了烧烤,还在另一家订了啤酒。打算等到烧烤和啤酒都配送成功后一起吃,美滋滋~~。比如:

Promise.all([订烧烤,订啤酒]).then(function(value){
    //吃烧烤,喝啤酒...
}, function(reason){
    //拒绝的原因,烤糊了或者啤酒卖完了...
})

这里要注意的一点是,对于数组中的promise,只要有任一个promise为拒绝,那么就会立即执行then中的拒绝处理函数,并不会等待其他promise的结果。只有当所有promise的结果都成功时,才执行then中的成功处理函数。比如:

var p1 = new Promise(function(resolve, reject){
  setTimeout(function(){
    console.log("A");
    resolve();
  }, 1000)
});
var p2 = Promise.reject(new Error("error"));
var p3 = new Promise(function(resolve, reject){
  setTimeout(function(){
    console.log("B");
    resolve();
  }, 0)
});
Promise.all([p1,p2,p3]).then(function(value){
  console.log("success!");
}, function(reason){
  console.log("failed");
})
//结果为failed B  A

由于p2为已拒绝状态的promise,所以Promise.all()立即变为拒绝状态,打印failed,p1和p2会继续执行,但对于Promise.all()的结果没有影响。

Promise.race()处理多个promise

Promise内建对象上的静态方法Promise.race()同样用于处理多个promise的情况。同样返回一个Promise,同样接收一个promise数组作为参数。

与all不同的地方在于,数组中的promise就像在赛跑一样(race),并且只关心第一名的情况,只要有其中一个promise有了结果,Promise.race()的状态就会立即与该promise相同。
数组中其他promise继续执行,但对于Promise.race()的结果没有影响。

注意事项

我们构造promise实例的代码是立即执行的,而then方法中的回调函数是异步调用的,在promise的状态变为成功或拒绝时,才会把相应的处理函数添加到promise工作队列中。并且该函数会先于setTimeout执行。例如:

var promise = new Promise(function(resolve, reject){
  console.log("A");
  resolve("C");
})

console.log("B");

setTimeout(function(){
  console.log("D");
},0)

promise.then(function(value){
  console.log(value)
});
//打印A, B, C, D

如果then方法中传入的参数被忽略,或者是非函数,比如:

p.then(function(value){
    //...
})
//或者
p.then(undefined, function(reason){
    //...
})

那么,相应的回调处理函数被忽略,then方法返回的promise会保留上一个promise的状态和参数。最典型的例子:

var p = new Promise(function(resolve, reject){
    reject(new Error("error"));
})
p.then(function(value){
    //...
}).then(function(value){
    //...
}).then(undefined, function(reason){
    console.log(reason);
})
//打印"error"

p的前两次then调用的拒绝处理函数被忽略,然后reject状态和错误信息就一直往后传递,直到被最后一次then调用捕获。

最佳实践

关于Promise的知识点很多,但是最常用的场景就是Ajax。比如:

function getData(method, url){
  var promise = new Promise(function(resolve, reject){
    //Ajax获取数据的代码...
    if(success){
      resolve(response)
    }else{
      reject(statusText)
    }
  })
  return promise;
}

getData("get","www.xxx.com").then(Fun1).then(Fun2).then(Fun3).catch(function(reason){
  //错误处理逻辑...
});

更多关于es6的内容,可以关注右侧我的专栏--学习ES6。

参考:
MDN Javascript Promise.
Promise介绍-基础篇.
《深入理解ES6》-- Promise与异步编程。

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

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

相关文章

  • ES6-7

    摘要:的翻译文档由的维护很多人说,阮老师已经有一本关于的书了入门,觉得看看这本书就足够了。前端的异步解决方案之和异步编程模式在前端开发过程中,显得越来越重要。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。 JavaScript Promise 迷你书(中文版) 超详细介绍promise的gitbook,看完再不会promise...... 本书的目的是以目前还在制定中的ECMASc...

    mudiyouyou 评论0 收藏0
  • 通过 ES6 Promise 和 jQuery Deferred 的异同学习 Promise

    摘要:和和都有和,但是略有不同。实际上返回的是一个对象。和添加的回调,添加的回调。所以在调用成功的情况下执行添加的回调,调用失败时执行添加的回调。,产生对象并,产生对象并,然后继续处理,的语法糖,和的差不多但不同。 Deferred 和 Promise ES6 和 jQuery 都有 Deffered 和 Promise,但是略有不同。不过它们的作用可以简单的用两句话来描述 Deffere...

    Yujiaao 评论0 收藏0
  • JavaScript 异步

    摘要:从最开始的到封装后的都在试图解决异步编程过程中的问题。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。写一个符合规范并可配合使用的写一个符合规范并可配合使用的理解的工作原理采用回调函数来处理异步编程。 JavaScript怎么使用循环代替(异步)递归 问题描述 在开发过程中,遇到一个需求:在系统初始化时通过http获取一个第三方服务器端的列表,第三方服务器提供了一个接口,可通过...

    tuniutech 评论0 收藏0
  • 浅谈ES6原生Promise

    摘要:如果有错误,则到的第二个回调函数中,对错误进行处理。假设第一个的第一个回调没有返回一个对象,那么第二个的调用者还是原来的对象,只不过其的值变成了第一个中第一个回调函数的返回值。 ES6标准出炉之前,一个幽灵,回调的幽灵,游荡在JavaScript世界。 正所谓: 世界本没有回调,写的人多了,也就有了})})})})})。 Promise的兴起,是因为异步方法调用中,往往会出现回调函数一...

    yedf 评论0 收藏0
  • ES6Promise:要优雅,也要浪漫

    摘要:就算改变已经发生了,即使再对对象添加回调函数,也会立即得到这个结果。方法接收个参数,第一个参数是状态的回调函数,第二个参数可选是状态的回调函数。简单来讲,就是能把原来的回调写法分离出来,在异步操作执行完后,用链式调用的方式执行回调函数。 在ECMAScript 6标准中,Promise被正式列为规范,Promise,字面意思就是许诺,承诺,嘿,听着是不是很浪漫的说?我们来探究一下这个浪...

    weizx 评论0 收藏0

发表评论

0条评论

wemallshop

|高级讲师

TA的文章

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