小白也要学 Promise
@CSS魔法
2016. 04. 22.
(入门篇)
- Promise 是什么?
- Promise 有什么用?
- 如何用 Promise 改善既有代码?
(30~45 分钟)
Promise 是什么?
这是一个自取其辱的问题……
- 懂的人本来就懂
- 不懂的人还是不懂
Promise 就是 Promise 呀!!
Promise 就是对未来的 “承诺” 呀!
Promise 是一种异步编程方式。
所以……
- 先别管它是什么了
- 直接看它能做什么就好了
Promise 有什么用?
- 获知未来的状态变化
- 获取未来的值
- 管理多个异步过程
优雅满足三种需求:
“通过什么来做到?”
先看最基本的 API。
// 有一个 Promise
var myPromise = (/* 通过某些黑魔法创建了一个 Promise */)
// 刚刚创建的 Promise 就像一个装了礼物的盒子,现在还不能拆
// 但在未来的某个时间点,盒子会自己打开
// 到时候你就知道里面装的是什么了
// 但你现在能做的,只是先想好到时候你要做什么
myPromise.then(function (content) {
// 通过这个 “回调函数” 来约定你要对盒子里的内容做什么
console.log(content)
})
// 有一个 Promise
var myPromise = {/* 通过某些黑魔法创建了一个 Promise */}
// 还有些时候,我们只关心盒子什么时候打开
// 并不关心盒子里有什么东西
myPromise.then(function () {
// 通过这个 “回调函数” 来约定当盒子打开时你要做的事儿
console.log('It is time.')
})
获知未来的状态变化
- 以事件的方式
- 以回调函数的方式
- 以 Promise 的方式
在 JavaScript 的世界里,外部环境如果要通知你某个状态在未来发生了变化,有哪些做法?
以 DOM Ready 为例吧……
以事件的方式
// Whatwg API
document.addEventListener('DOMContentLoaded', function () {
/* init widgets here */
});
以回调函数的方式
// 万能的 jQuery
var $ = require('jquery')
$(function () {
/* init widgets here */
});
以 Promise 的方式
// 假设有一个类库,它返回一个 Promise
// 这个 Promise 在 DOM Ready 时打开……
var promiseDomReady = require('promise-dom-ready')
promiseDomReady.then(function () {
/* init widgets here */
});
貌似并没有什么卵用?
获取未来的值
- 以回调函数的方式
- 以事件的方式
- 以 Promise 的方式
有一个值,现在还不存在,在等到未来某个时间点才能拿到。在 JavaScript 世界中,如何实现?
比如,我们需要获取用户输入的某个值……
button.submit
请输入一个值:
#dialog
input.text
以回调函数的方式
// 先想好拿到值后要做什么
function handleUserInput(value) {
console.log(value)
}
// 监听用户行为,等待用户提供值
$('#dialog button.submit').once('click', function () {
$('#dialog').close()
var value = $('#dialog input.text').val()
// 当我们需要值出现时,让预先定义好的回调函数来处理它
handleUserInput(value)
});
$('#dialog').popup()
“用值” 的代码混在 “等值” 的代码中了
以事件的方式
// 监听用户行为,等待用户提供值
$('#dialog button.submit').once('click', function () {
$('#dialog').close()
var value = $('#dialog input.text').val()
// 通过自定义事件把值抛出
$(window).trigger('user-input', {value: value})
});
$('#dialog').popup()
// 监听这个自定义事件,以便在未来获取抛出的值
$(window).on('user-input', function (event, param) {
console.log(param.value)
})
“等值” 的代码和 “用值” 的代码分得比较清楚了。
以 Promise 的方式
// 把用户在未来提供的值包装成一个 Promise
var promiseUserInput = {/* 通过某些黑魔法创建了一个 Promise */}
// 等值可用时,我们来处理它
promiseUserInput.then(function (value) {
console.log(value)
})
很清爽,有木有?
但是……“黑魔法” 部分属于作弊吧?
对应用层来说,连对话框的弹出和关闭都不需要操心了。
来看看 “黑魔法”
第二个重要的 API:
如何创建一个 Promise?
// Promise 构造器
var myPromise = new Promise(function (resolve, reject) {
// 我们在这里决定这个 Promise 如何、何时得到未来的值
// 再用 resolve() 函数来把值抛出来
resolve(...)
})
// Promise 构造器
var myPromise = new Promise(function (resolve, reject) {
// 我们在这里决定这个 Promise 如何、何时得到未来的值
// 再用 resolve() 函数来把值抛出来
// 不过一般来说,resolve() 都不会是同步调用
// 它往往在一个异步过程中被调用,比如:
setTimeout(function () {
resolve(...)
}, 1000)
})
我们来把那个 “等待用户输入”
的例子写完整。
// 把用户在未来提供的值包装成一个 Promise
var promiseUserInput = {/* 通过某些黑魔法创建了一个 Promise */}
// 等值可用时,我们来处理它
promiseUserInput.then(function (value) {
console.log(value)
})
我们没写完的代码:
// 把用户在未来提供的值包装成一个 Promise
var promiseUserInput = new Promise(function (resolve, reject) {
$('#dialog').popup()
$('#dialog button.submit').once('click', function () {
$('#dialog').close()
var value = $('#dialog input.text').val()
// 等值出现时,把它抛出
resolve(value)
});
})
目前为止我们感受到的 Promise 的好处:
- 把异步逻辑包装在它内部
- 对外暴露清爽的接口
我差不多懂了,我们找个案例来试试 Promise 吧!
我年轻的时候写过一个组件叫 XStorage:
// 以前设计的 API 一般采用回调函数方式
XStorage.getItem = function (key, callback) {
/* ... */
}
XStorage.setItem = function (key, value, callback) {
/* ... */
}
它用于跨域存取本地存储。
它通过 postMessage()
来实现跨域通讯,因此天生是异步的。
能用,但不好看,也不好用。
如果给我一个重新设计 API 的机会……
如果给我一个重新设计 API 的机会……
// 所有 API 返回 Promise
XStorage.getItem = function (key) {
return new Promise(function (resolve, reject) {
/* ... */
})
}
XStorage.setItem = function (key, value) {
return new Promise(function (resolve, reject) {
/* ... */
})
}
// 在调用时
XStorage.getItem('key').then(...)
XStorage.setItem('key', 1).then(...)
管理多个异步过程
这才是 Promise 真正的威力所在。
Task 1
● 先看多个 “串行” 的异步过程
Task 2
Task 3
Task 4
在旧社会,我们是这样写的:
在旧社会,我们是这样写的:
// 我们有一些异步任务,每个任务完成后会调用传给它的回调函数
function asyncTask1(callback) {...}
function asyncTask2(callback) {...}
function asyncTask3(callback) {...}
function asyncTask4(callback) {...}
// 通过回调函数来实现顺序依赖
asyncTask1(function () {
asyncTask2(function () {
asyncTask3(function () {
asyncTask4(function () {
console.log('All done!')
})
})
})
})
传说中的 “回调金字塔”!
“Promise 对此有什么帮助?”
关于 .then()
这个 API 的真相……
真相一
每次调用 .then()
都返回一个新的 Promise
看代码:
// 有一个 Promise
var myPromise = new Promise(...)
myPromise
.then(...) // 返回一个新的 Promise
.then(...) // 又返回一个新的 Promise
//...
可以一直 .then()
下去!
“链式调用”!
看代码:
(如果其返回值是 Promise,则 .then()
将其直接返回。)
真相二
每个 Promise 都可以包装一个值,
这个新的 Promise 包装的值从哪里来?
.then()
的回调函数的返回值
将被包装在这个新 Promise 中
myPromise.then(function () {
/* 做了一些事 */
return something
})
看个例子:
// 有一个 Promise
var myPromise = new Promise(...)
myPromise
.then(function (value) {
return handle(value)
}) // 返回一个 Promise,它包装的值是上一个 Promise 包装的值处理后的结果
.then(function (value) {
var promiseAjax = new Promise (function (resolve) {
$.post(url, {qty: value}, function (data) {
resolve(data)
})
})
return promiseAjax
}) // 直接返回 promiseAjax
看个例子:
好,带着新技能来试试
如何组织多个串行的异步任务。
在 Promise 时代:
在 Promise 时代:
// 我们有一些异步任务,每个任务均返回一个 Promise
function asyncTask1() {...}
function asyncTask2() {...}
function asyncTask3() {...}
function asyncTask4() {...}
// 通过 .then() 来实现顺序依赖
asyncTask1().then(function () {
return asyncTask2()
}).then(function () {
return asyncTask3()
}).then(function () {
return asyncTask4()
}).then(function () {
console.log('All done.')
})
还可以简化!
在 Promise 时代:
// 我们有一些异步任务,每个任务均返回一个 Promise
function asyncTask1() {...}
function asyncTask2() {...}
function asyncTask3() {...}
function asyncTask4() {...}
// 通过 .then() 来实现顺序依赖
asyncTask1()
.then(asyncTask2)
.then(asyncTask3)
.then(asyncTask4)
.then(function () {
console.log('All done.')
})
好,接下来看看
多个 “并行” 任务是什么情况。
Task 1
● 获知多个 “并行” 异步过程全部结束的时间点
Task 2
Task 3
Task 4
又要 Get 新 API 了:
// 这个 API 可以监控多个 Promise 的状态
// 它接受多个 Promise 组成的数组,返回一个 Promise
// 当所有 Promise 都完成后,它自己也会完成。
Promise.all([
promise1,
promise2,
...
])
Promise.all()
来试试新技能:
来试试新技能:
// 我们有一些异步任务,每个任务均返回一个 Promise
function asyncTask1() {...}
function asyncTask2() {...}
function asyncTask3() {...}
function asyncTask4() {...}
// 通过 Promise.all() 来监控多个并行任务何时全部完成
Promise.all([
asyncTask1(),
asyncTask2(),
asyncTask3(),
asyncTask4(),
])
.then(function () {
console.log('All done.')
})
Task 1
● 获知多个 “并行” 异步过程中谁最先结束
Task 2
Task 3
Task 4
还有一个新 API:
// 这个 API 可以监控多个 Promise 的状态
// 它接受多个 Promise 组成的数组,返回一个 Promise
// 当其中有一个 Promise 完成时,它自己也会完成。
Promise.race([
promise1,
promise2,
...
])
Promise.race()
来试试看,其实跟刚才的例子差不多:
来试试看,其实跟刚才的例子差不多:
// 我们有一些异步任务,每个任务均返回一个 Promise
function asyncTask1() {...}
function asyncTask2() {...}
function asyncTask3() {...}
function asyncTask4() {...}
// 通过 Promise.race() 来监控多个并行任务中谁最先完成
Promise.race([
asyncTask1(),
asyncTask2(),
asyncTask3(),
asyncTask4(),
])
.then(function () {
console.log('At less one task done.')
})
用 Promise 改善既有代码
(Demo)
Q & A
Thank You!
小白也要学 Promise(入门篇)
By CSS魔法
小白也要学 Promise(入门篇)
初学者也能看懂的 Promise 入门教程,因为我也是一个初学者。
- 1,157