ES6 Promise 对象及解决回调地狱问题

概述

在JavaScript的世界中,所有代码都是单线程执行的。

由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。

AJAX就是典型的异步操作。

把回调函数success(request.responseText)fail(request.status)写到一个AJAX操作里很正常,但是不好看,而且不利于代码复用。

有没有更好的写法?比如写成这样:

var ajax = ajaxGet('http://...');
ajax.ifSuccess(success)
    .ifFail(fail);

先统一执行AJAX逻辑,不关心如何处理结果,然后,根据结果是成功还是失败,在将来的某个时候调用success函数或fail函数。这种“承诺将来会执行”的对象在JavaScript中称为Promise对象。

实例

我们先看一个最简单的Promise例子:生成一个0-2之间的随机数,如果小于1,则等待一段时间后返回成功,否则返回失败:

function test(resolve, reject) {
    var timeOut = Math.random() * 2;
    console.log('set timeout to: ' + timeOut + ' seconds.');
    setTimeout(function () {
        if (timeOut < 1) {
            console.log('call resolve()...');
            resolve('200 OK');
        }
        else {
            console.log('call reject()...');
            reject('timeout in ' + timeOut + ' seconds.');
        }
    }, timeOut * 1000);
}

 

可以看出,test()函数只关心自身的逻辑,并不关心具体的resolvereject将如何处理结果。

有了执行函数,我们就可以用一个Promise对象来执行它,并在将来某个时刻获得成功或失败的结果:

var p1 = new Promise(test);
var p2 = p1.then(function (result) {
    console.log('成功:' + result);
});
var p3 = p2.catch(function (reason) {
    console.log('失败:' + reason);
});

Promise对象可以串联起来,所以上述代码可以简化为:

new Promise(test).then(function (result) {
    console.log('成功:' + result);
}).catch(function (reason) {
    console.log('失败:' + reason);
});

可见Promise最大的好处是在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了:

 

 

Promise.all()  这两个任务是可以并行执行的

var p1 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([p1, p2]).then(function (results) {
    console.log(results); // 获得一个Array: ['P1', 'P2']
});

Promise.race() 多个异步任务是为了容错,只需要获得先返回的结果即可。

var p1 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
    setTimeout(resolve, 600, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
    console.log(result); // 'P1'
});

Promise 状态

Promise 异步操作有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。除了异步操作的结果,任何其他操作都无法改变这个状态。

Promise 对象只有:从 pending 变为 fulfilled 和从 pending 变为 rejected 的状态改变。只要处于 fulfilled 和 rejected ,状态就不会再变了即 resolved(已定型)。

then 方法

then 方法接收两个函数作为参数,第一个参数是 Promise 执行成功时的回调,第二个参数是 Promise 执行失败时的回调,两个函数只会有一个被调用。

在 JavaScript 事件队列的当前运行完成之前,回调函数永远不会被调用。

通过 .then 形式添加的回调函数,不论什么时候,都会被调用。

 

回调函数 VS Promise

之前大家解决异步事件都是用回调函数去解决,但是回调函数写异步会出现一些问题,回调地狱的问题。

那么promise就是一种新的处理异步的方法,可以完美的解决回调函数处理异步带来的问题(回调地狱问题)。

其实promise就是异步操作的方法,就像之前的定时器一样,看到定时器就知道这是异步操作,同样,看到promise就知道这个是异步操作。
 
回调地狱问题
在使用JavaScript时,为了实现某些逻辑经常会写出层层嵌套的回调函数,如果嵌套过多,会极大影响代码可读性和逻辑,这种情况也被成为回调地狱。代码阅读性差。
比如用户登录时,首先验证用户信息并返回,获取到的用户信息后根据用户信息获取金币数量,以及用户的相关权限等。很容易形成回调地狱。(梁涛注)
var sayhello = function (name, callback) {
  setTimeout(function () {
    console.log(name);
    callback();
  }, 1000);
}
sayhello("first", function () {
  sayhello("second", function () {
    sayhello("third", function () {
      console.log("end");
    });
  });
});
//输出: first second third  end

解决回调地狱有很多方法,比如:Promise 对象、Generator 函数、async 函数

Promise 对象解决回调地狱

var sayhello = function (name) {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      console.log(name);
      resolve();  //在异步操作执行完后执行 resolve() 函数
    }, 1000);
  });
}
sayhello("first").then(function () {
  return sayhello("second");  //仍然返回一个 Promise 对象
}).then(function () {
  return sayhello("third");
}).then(function () {
  console.log('end');
}).catch(function (err) {
  console.log(err);
})

 另外一个实例

    function f1(){
        return new Promise(resolve=>{
            setTimeout(()=>{
                console.log('第一步');
                //异步逻辑已经执行完,必须要告诉外界我执行完了
                resolve();

            },1000)
        })
    }
    function f2(){
        return new Promise(resolve=>{
            setTimeout(()=>{
                console.log('第二步');
                //告诉外界我执行完了
                resolve();
            },1000)
        })
    }
    f1().then(res=>{
        return f2();
    }).then(res=>{
        return f1();
    }).then(res=>{
        return f2();
    }).then(res=>{
        setTimeout(()=>{
            console.log('完成');
        },1000)
    })

参考原文 参考原文2

posted @ 2019-10-21 22:44  梁涛999  阅读(455)  评论(0编辑  收藏  举报