【JavaScript】Promise教程、Promise的使用。

Promise为异步任务提供了非常棒的解决方案,本文介绍了如何使用Promise完成一系列异步任务操作。

【JavaScript】Promise教程、Promise的使用。

by img MicroAnswer Create at:Sep 12, 2019 9:17:56 AM 

Tags: promise javascript 异步

Promise为异步任务提供了非常棒的解决方案,本文介绍了如何使用Promise完成一系列异步任务操作。


重要声明:本文章仅仅代表了作者个人对此观点的理解和表述。读者请查阅时持自己的意见进行讨论。

Promise 是 ES6 加入的特性,它让异步任务更加合理和强大。越来越多的人开始使用它,我们也不能坐视不管,技术要点也必须要收入囊中。

尝鲜

正式介绍之前,先看一看Promise用起来通常是一个怎么样的体验。下面给出一个使用示列:

// 1
new Promise(function(resolve, reject) {
    try{
        var response = ajaxPost('https://www.baidu.com', {key: "Microanswer"})
        resolve(response)
    } catch (err) {
        reject(err)
    }

// 2
}).then(function(response) {
    if (response.code === 200) {
        return buildResult(response.data)
    } else {
        throw new Error("请求失败。")
    }

// 3
}).then(function (data) {
    render(data)

// 4
}).catch(function (err) {
    renderErr(err)
})

上述代码完成了一次请求,并将结果渲染到界面上。从代码标注上可看到,从请求到显示出数据一共分为了3步,第4步为错误结果展示。

第一步:发出一个请求Post请求。并使用try..catch进行异常捕获,这样可以捕获到因网络不通畅而导致的问题。

第二步:解析得到的请求返回内容,如果内容正确则构建结果。否则抛出错误。

第三步:渲染数据。

异常处理:此步在最后结尾处。前面任何一步,一但出错就会直接执行异常处理。

可以很容易发现,这其中没有出现过使用回调函数的方式来保证异步任务结束后逻辑的正常执行,相反是一个接一个的 then 在后面链在一起。无疑这带来了代码执行流程上直观体验。

一、Promise 对象的创建

Promise 对象的创建方式常见的有两种,一种通过构造函数、另一种则通过其提供的静态方法。

1、通过构造函数

这种方式允许你兼容曾经使用回调函数来完成的异步任务。下面展示一个典型的创建方法:

var promise = new Promise(function (resolve, reject) {
    resolve("Hello Promise!")
})

这段代码构造了一个非常简单的promise对象。如果你还不了解其含义,下面将详细的进行讲解。现在先把目光放在Promise构造函数本身上。这是一个非常简单的构造函数,要成功构造Promise对象,只需要提供一个参数,这个参数是一个Function类型的即可,如上代码所示。当promise执行时,会调用这个方法,并传入两个参数,它们分别是 resolve 和 reject。

1)、resolve 参数

当你传递的 Function 执行完成后,必须要产生一个结果,为了Promise的正确执行,你需要告诉Promise,你的执行结果是正确,还是错误的。resolve 其实是一个方法,当你结果正确时,可通过调用 resolve(),标记执行完成并且结果正确,正确可以没有参数。如果你有参数则可以将参数放入其中,比如:resolve("Jack")。这就相当于完成了正确任务的标记,并将结果传达了下去。在此promise的下一个then里面,就可以拿到你传入的 "Jack" 了。例如:

var promise = new Promise(function (resolve, reject) {
    resolve("Jack");
})
promise.then(function (result) {
    console.log(result) // 输出:Jack
})
2)、reject 参数

无论什么任务,又特别是异步的耗时任务,出错是肯定避免不了的。因此当你的 Function 执行过程中出现了错误,reject 允许你把错误告诉给Promise。假如你的 Promise后面已经有许许多多的 then 任务节点,那么这些节点就都不会执行了,Promise 会一直寻找下面的catch方法,如果没找到则会报错:发现未捕获的异常xxx。下面示列一个典型的出错场景:

var promise = new Promise(function (resolve, reject) {
    reject(new Error("出错了"));
})
promise.then(function (result) {
    console.log(result) // 不会执行
}).catch(function (result) {
    console.log(result) // 打印:出错了
})

可以看到,在第一个构造函数内,直接使用 reject 指明出错了,Promise发现出错,开始向下寻找catch,直到最后发现一个catch,那么此时catch就立即执行,打印了错误信息。

通过构造函数创建promise实例现在有了一个基本的理解,因为其提供了resolve和reject,则将以前的异步任务(回调方法)得到了完美的兼容。

举例:在使用Promise前,就已经开发好了一个网络请求方法,使用了回调函数来获取请求结果,方法原型就像这样:

function post(url, param, callback) {
    $.ajax({
        ...,
        success: function (res) {callback(null, res)}
        error: function (err) {callback(err, null)}
    })
}

// 正常使用
post("https://www.baidu.com", {key: "Jack"}, function (err, res) {
    // 拿到请求结果。
})

现在有了Promise的加入,希望不要再这样使用回调函数,从而实现获取到请求结果。那么就可以使用Promise构造函数进行一次封装:

function postByPromise(url, param) {
   return new Promise(function (resolve, reject) {
       post(url, param, function (err, res) {
            if (err) reject(err) else resolve(res)
       })
   })
}

// 使用
postByPromise("https://www.baidu.com", {key: "Jack"})
    .then(function (res) {
        // 拿到请求结果。
    })

通过上述代码,可以看到,原来通过回调函数实现的异步任务,可以完美的使用Promise进行一次封装,从而实现链式的调用方式。

2、通过静态方法创建

通过静态方法创建promise对象同样可以使用 then 向后不停的链式调用。创建起来更加简单,甚至不需要你实现一点逻辑就可以创建一个promise对象。例如:

var promise = Promise.resolve("Jack");
promise.then(function (result) {
    console.log(result) // 打印:Jack
})

十分简单对不。这样创建出来的promise对象是一个明确标记为正确的promise对象,它的第一个catch永远都不会执行,除了创建一个正确的promise对象,Promise还提供了一个创建错误promise对象的方法:

var promise = Promise.reject("出错了");
promise.catch(function (err) {
    console.log(err); // 打印:出错了
})

二、进一步使用Promise

当你了解了上面的内容后,就可以开始进一步使用Promise来完成任务了。它还有许多特性是上面没有介绍到的,接下来就结合代码进行讲解。

1、出错后继续

上面的代码里,catch代码始终都在最后一个,但肯定不是每次任务执行步骤都只需要最后一个错误捕获,还有很多时候是要在中途就捕获,然后还有针对错误的任务处理的。下面来个示列:

Promise.resolve()
    .then(function () {
        return "name"                // 执行
    }).then(function (name) {
        return Promise.reject("出错") // 构建错误并返回
    }).then(function (resut) {
        return "age"                  // 不执行,因为错误还没被捕获。
    }).catch(function (err) {
        console.log(err)              // 执行,打印错误信息。
        return "处理了出错"
    }).then(function (msg) {
        comsole.log(msg)              // 执行,因为错误已被捕获且又返回了正确的msg
    })

上述代码中,中途构建了错误promise并返回了,后续发现有错误,就不会执行后续的then,可以看到紧接着的then并没有执行,然后紧接着发现catch,将错误交给这个catch运行,catch运行后返回了一个字符串,只要返回的不是错误的promise,那么Promise就都认为错误已经处理了,便会开始继续执行下面所有的then。

2、多个Promise

假如有这样一个需求:现在要同时发送3个请求,但业务逻辑必须等所有请求完成后进行,这时,如果用传统回调方式去处理,势必要写很多代码去维护是3个请求都完成了。现在有了 Promise 就可以非常方便完成这个需求:

var req1 = postByPromise("/xxx", {...})
var req2 = postByPromise("/zzz", {...})
var req3 = postByPromise("/yyy", {...})

// 全部同时执行
Promise.all([req1, req2, req3])
    .then(function (result) {
        var result1 = result[0];
        var result2 = result[1];
        var result3 = result[2];
        // 三个请求都完成了,并且拿到了结果。
    })

3、出错后重试

经常网络请求出现失败,但是重新请求却又正常了,这时我们可以做一个出错失败的机制,让每次执行出错时都重试,并重试指定最大次数,超过次数则最终报错。

function postByPromiseTryCount(url, param, maxTryCount) {
    var tryCount = 0

    function d0(url, param) {
        return postByPromise(url, param).catch(function (err) {

            // 重试次数低于最大次数,可以继续重试。
            if (++tryCount < maxTryCount) {
                return d0(url, param)
            } else {
                return Promise.reject(err)
            }
        })
    }

    return d0(url, param)
}

上述 postByPromiseTryCount 方法是对 postByPromise 方法的封装,支持了失败自动重试功能。

4、注意事项

Promise尽管好用,但使用不当也会出现许多问题。以下罗列了我在使用时遇到的问题。

  1. 结合for循环使用时,如果是前端,最好使用封装的遍历方法,类似$.each之类的,而杜绝直接使用for循环。
  2. Promise内执行的任务都是异步的,并不是写在前面的代码就先执行,但在promise外部的代码总是先运行。
  3. 每一个then方法都可以传递2个参数,第一个是正确时执行的,第二个时错误时执行的,你可以不写catch函数,而把错误捕获函数放在第二个参数。
  4. 每个then方法都会返回一个新的promise对象,即便是你没有返回值,也会返回一个值为空的promise的对象,意味着你也可以继续then。
  5. 所有任务你如果要标记出错了,可通过返回错误的promise,也可以直接throw错误信息。

三、写在最后

本文只介绍了Promise的基本初级使用方式,还有许多高级用法没有介绍,希望更深入了解,可以点击这里查看。

Full text complete, Reproduction please indicate the source. Help you? Not as good as one:
Comment(Comments need to be logged in. You are not logged in.)
You need to log in before you can comment.

Comments (1 Comments)