Javascript 语言的执行环境是“单线程”(single thread)。所谓“单线程”,就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务。
这就引发了同步和异步的问题。
同步(Synchronous)就是后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的。这往往用于一些简单的、快速的、不涉及 IO 读写的操作。
例1:
// task 1
while(true) {
}
// task 2
console.log('done')
本例中,task1和task2是同步的、顺序执行的。由于task1是死循环,所以task2实际上并没有机会运行。
异步(Asynchronous)任务分成两段,第一段代码包含对外部数据的请求,第二段代码被写成一个回调函数,包含了对外部数据的处理。第一段代码执行完,不是立刻执行第二段代码,而是将程序的执行权交给下一个任务。等到外部数据返回了,再由系统通知执行第二段代码。所以,程序的执行顺序与任务的排列顺序是不一致的、异步的。
例2:
// task 1
setTimeout(() => {
console.log('aaa')
}, 1000)
// task 2
console.log('bbb')
运行程序,先打印 bbb
,1秒钟之后,再打印 aaa
:
bbb
aaa
注:即使把定时器的延时改为0,也会先打印 bbb
,再打印 aaa
,这是因为异步任务会在当前脚本的所有同步任务执行完才会执行。显然,如果task 2包含了死循环,则异步任务就不会运行。
在上面的例子中,setTimeout()
方法的参数就是一个回调函数(Callback)。
所谓回调函数,就是一段可执行的代码,以参数的形式传递给其它代码,在合适的时机被调用。
回调函数既可以用于异步,也可以用于同步。
下面是一个同步回调的例子。
例3:
function f1(x) {
x += 100
console.log(x)
}
function f2(y, fun) {
y += 20
fun(y)
}
f2(50, f1)
程序运行结果为 170
。
本例中,把 f1()
函数作为回调函数传递给了 f2()
函数。这是一个同步的回调函数。
像 setTimeout()
、ajax请求等,就是异步回调。
下面是一个异步回调的例子。
例4:
function f1(param1, param2, succFun, errFun) {
setTimeout(() => {
if (param2 == 0) {
errFun('Divide by zero')
} else {
let result = param1 / param2
succFun(result)
}
}, 1000)
}
function succ(data) {
console.log('Result is ' + data)
}
function err(msg) {
console.log('Error! Message is: ' + msg)
}
f1(100, 2, succ, err)
// other tasks
console.log('Do something...')
程序运行结果如下:
Do something...
Result is 50
如果把第二个参数改为0,则会触发 err()
函数:
Do something...
Error! Message is: Divide by zero
把例4改造成使用Promise,如下。
例5:
function f1(param1, param2, succFun, errFun) {
setTimeout(() => {
if (param2 == 0) {
errFun('Divide by zero')
} else {
let result = param1 / param2
succFun(result)
}
}, 1000)
}
function succ(data) {
console.log('Result is ' + data)
}
function err(msg) {
console.log('Error! Message is: ' + msg)
}
new Promise((resolve, reject) => {
f1(100, 2, resolve, reject)
}).then(succ, err)
// other tasks
console.log('Do something...')
运行结果和例4一样。
本例和例4很像,似乎没什么差异。实际上,Promise的一个优点在于它的多重链式调用,可以避免层层嵌套回调。
(注:例5和例4只是“看上去很像”,二者还是有根本差异的。Promise对象在创建后,会立即同步运行,但是 resolve()
、 reject()
方法会异步运行。比如,如果在 f1()
方法里,最前面加上一行代码 succFun(1234)
:
Result is 1234
Do something...
Result is 50
Do something...
Result is 1234 # 因为resolve()是异步运行的,而且resolve()只会运行一次
如果不是很明白,不要着急,看下面对Promise的讲解。)
假设由于需求变化,现在要对例4做扩展,接连对数据做四次运算。
例6:
function f1(param1, param2, succFun, errFun) {
setTimeout(() => {
if (param2 == 0) {
errFun('Divide by zero')
} else {
let result = param1 / param2
succFun(result)
}
}, 1000)
}
function err(msg) {
console.log('Error! Message is: ' + msg)
}
f1(100, 2, data1 => {
console.log('Result is ' + data1)
f1(200, data1, data2 => {
console.log('Result is ' + data2)
f1(300, data2, data3 => {
console.log('Result is ' + data3)
f1(400, data3, data4 => {
console.log('Result is ' + data4)
// ...
}, err)
}, err)
}, err)
}, err)
// other tasks
console.log('Do something...')
程序运行结果如下:
Do something...
Result is 50
Result is 4
Result is 75
Result is 5.333333333333333
嵌套的调用关系使得代码的可读性和可维护性都变得很差。
使用Promise,则可以用 then()
进行链式回调,避免多重嵌套。
例7:
function f1(param1, param2, succFun, errFun) {
setTimeout(() => {
if (param2 == 0) {
errFun('Divide by zero')
} else {
let result = param1 / param2
succFun(result)
}
}, 1000)
}
function err(msg) {
console.log('Error! Message is: ' + msg)
}
new Promise((resolve, reject) => {
f1(100, 2, resolve, reject)
}).then(data1 => {
console.log('Result is ' + data1)
return new Promise((resolve, reject) => {
f1(200, data1, resolve, reject)
})
}).then(data2 => {
console.log('Result is ' + data2)
return new Promise((resolve, reject) => {
f1(300, data2, resolve, reject)
})
}).then(data3 => {
console.log('Result is ' + data3)
return new Promise((resolve, reject) => {
f1(400, data3, resolve, reject)
})
}).then(data4 => {
console.log('Result is ' + data4)
// ...
}).catch(error => {
err(error)
})
// other tasks
console.log('Do something...')
Promise对象代表一个未完成、但预计将来会完成的操作。
注意:Promise一旦新建就会立即执行,无法取消,这是一个同步操作。
Promise对象有以下三种状态:
pending
:初始值fulfilled
:代表操作成功rejected
:代表操作失败Promise可以从pending转变为fulfilled,也可以从pending转变为rejected。一旦状态改变,就“凝固”了,会一直保持这个状态,不会再发生变化。
当状态发生变化, then()
绑定的函数就会被调用。
Promise构造函数有一个参数,该参数是一个函数,有两个参数resolve和reject,分别代表成功和失败的回调函数。也就是说,在异步操作成功时,应调用resolve函数,而在异步操作失败时,应调用reject函数。
例8:
var promise = new Promise((resolve, reject) => {
// 异步操作
......
if (/* 异步操作成功 */) {
resolve(data)
} else {
/* 异步操作失败 */
reject(error)
}
})
接下来,可以用 then()
方法指定resolve和reject回调函数。
promise.then(data => {
// 处理data
......
}, error => {
// 处理error
......
})
会不会出现这种情况:到了该执行回调函数的时候,却尚未还没有指定回调函数。我的理解是,不会出现这种情况,因为 then()
是同步操作,而 resolve()
是异步操作,正如前面提到的,异步操作会在同步操作之后才会运行。
例9:
let promise = new Promise((resolve, reject) => {
console.log('start')
let a = 1
if (a == 1)
resolve(111)
else
resolve(222)
console.log('end')
})
// 循环会花费几秒钟时间,这是为了晚一点指定回调函数
let x = 0
for(let i = 0; i < 5000000000; i++) {
x++;
}
console.log('x = ' + x)
promise.then(data => console.log('Result is: ' + data))
本例中,在Promise里直接调用 resolve()
方法,并没有其它异步操作,而 resolve()
方法本身是异步的,所以会在 then()
方法之后才会执行。
程序运行结果如下:
start
end
x = 5000000000
Result is: 111
注意: start
和 end
是立刻输出的,因为它们都是同步操作(前面提到,Promise构造函数是同步运行的),然后过了几秒钟,才输出 x = 5000000000
(这也是同步操作),最后输出 Result is: 111
(这是异步操作)。
then()
和 catch()
then()
方法有两个参数,分别为Promise从 pending
变为 fulfilled
和 rejected
时的回调函数。这两个函数都接受Promise对象传出的值作为参数。 then()
方法返回一个新的Promise对象。
简单来说, then()
方法就是定义resolve和reject函数的。
更直白的讲:
resolve()
回调(在同步操作之后才会执行);then()
方法里指定 resolve()
回调;then()
方法也可以只有一个参数,即resolve回调函数。
catch()
方法是 then(undefined, onRejected)
的别名,用于指定发生错误时的回调函数。
例10:
let promise = new Promise((resolve, reject) => {
if (2 == 1)
resolve(123)
else
reject(456)
})
// 方法 1
promise.then(data => {
console.log('Result is: ' + data)
}, error => {
console.log('Error: ' + error)
})
// 方法 2
promise.then(data => {
console.log('Result is: ' + data)
}).catch(error => {
console.log('Error: ' + error)
})
// 方法 3
promise.then(data => {
console.log('Result is: ' + data)
}).then(undefined, error => {
console.log('Error: ' + error)
})
注意:方法1里,如果 resolve()
回调里报错,不会被 reject()
回调捕获。
reject(xxx)
相当于 throw new Error(xxx)
。
但是,二者有一个重要的差异: reject()
是异步操作。
例11:
let promise = new Promise((resolve, reject) =>{
console.log('start')
// 方法 1
reject(456)
// 方法 2
//throw new Error(456)
console.log('end')
})
promise.then(data => {
console.log('Result is: ' + data)
}).catch(error => {
console.log('Error: ' + error)
})
方法1输出结果如下:
start
end
Error: 456
方法2输出结果如下:
start
Error: Error: 456
原因很简单, reject()
是异步操作,而 throw
是同步操作。
then()
方法指定的回调如果报错,会一直向后传递,直到被捕获。参见例7,如果把2改成0,则四个 then()
方法的回调都不会执行,而是直接执行 catch()
方法的回调了。
Promise状态一旦改变就会凝固,不会再改变。比如,promise一旦resolve了,再reject,也不会变为rejected,也不会被catch。
例12:
let promise = new Promise((resolve, reject) =>{
console.log('start')
resolve(123)
reject(456)
//throw new Error(456)
console.log('end')
})
promise.then(data => {
console.log('Result is: ' + data)
}).catch(error => {
console.log('Error: ' + error)
})
输出结果如下:
start
end
Result is: 123
本例中, reject()
不起作用。
但是,如果把 reject()
换成 throw
,结果会是什么呢?
start
Result is: 123
解析: resolve()
是异步操作,在同步操作之后执行,但是它会立刻(同步)把Promise状态变为 fulfilled
,状态一旦改变就会凝固,因此,后面再抛异常,也不会触发状态改变,也就不会触发 catch()
,相当于异常被“默默的压抑住了”。当然,由于抛出异常,后面的 console.log('end')
也不会再执行。
换句话说, resolve()
会立即把Promise状态变成 fulfilled
,并且把resolve回调准备好,等同步操作完成后就会执行。
但是这里面有一个问题:
例13:
let promise = new Promise((resolve, reject) =>{
console.log('start')
resolve(123)
throw new Error(456)
console.log('end')
})
promise.then(data => {
console.log('Result is: ' + data)
}).catch(error => {
console.log('Error: ' + error)
})
throw new Error(789)
运行结果如下:
start
/home/ding/temp/temp0606/test13.js:17
throw new Error(789)
^
Error: 789
at Object. (/home/ding/temp/temp0606/test13.js:17:7)
at Module._compile (node:internal/modules/cjs/loader:1254:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1308:10)
at Module.load (node:internal/modules/cjs/loader:1117:32)
at Module._load (node:internal/modules/cjs/loader:958:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
at node:internal/main/run_main_module:23:47
请注意:Promise里面的 throw new Error(456)
并不起作用(因为是在resolve之后),但是主程序里的 throw new Error(789)
还是起作用的。由于抛出异常,resolve回调并没有执行。
两个throw操作都是同步操作,但显然Promise内部的throw行为有所不同,这一点要格外注意。
另一点要注意的是,Promise里异步操作的异常不会被Promise的 catch()
捕获。
例13.A:
console.log('start')
setTimeout(() => {
throw new Error(123)
}, 1000)
console.log('end')
运行结果如下:
start
end
/home/ding/temp/temp0606/test23.js:4
throw new Error(123)
^
Error: 123
at Timeout._onTimeout (/home/ding/temp/temp0606/test23.js:4:8)
at listOnTimeout (node:internal/timers:569:17)
at process.processTimers (node:internal/timers:512:7)
Node.js v18.16.0
例13.B:
let promise = new Promise((resolve, reject) => {
console.log('start')
setTimeout(() => {
throw new Error(123)
}, 1000)
console.log('end')
})
promise.then(data => {
console.log('Result is: ' + data)
}).catch(error => {
console.log('Error: ' + error)
})
运行结果如下:
start
end
/home/ding/temp/temp0606/test24.js:5
throw new Error(123)
^
Error: 123
at Timeout._onTimeout (/home/ding/temp/temp0606/test24.js:5:9)
at listOnTimeout (node:internal/timers:569:17)
at process.processTimers (node:internal/timers:512:7)
Node.js v18.16.0
可见,Promise里异步操作中的异常,不会被Promise的 catch()
方法所捕获。
all()
和 race()
语法:Promise.all(iterable)
比如: Promise.all([p1, p2, p3])
,将多个Promise实例,包装成一个新的Promise实例。当p1、p2、p3的状态都变成 fulfilled
时,该Promise的状态才变成 fulfilled
。
例14:
console.log('start')
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(100)}, 1000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(200)}, 5000)
})
let p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(50)}, 3000)
})
let promise = Promise.all([p1, p2, p3])
promise.then(data => {
console.log('Result is: ' + data)
})
console.log('end')
运行结果如下:
start
end
Result is: 100,200,50
程序立即输出 start
和 end
,大约5秒钟之后,输出 Result is: 100,200,50
。注意其顺序,是按位置排列,不是按resolve的时间顺序。
若内部多个Promise中任意一个状态变成 rejected
,则外部Promise立刻变成 rejected
。
例15:
console.log('start')
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(100)}, 1000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(200)}, 5000)
})
let p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p3')}, 3000)
})
let promise = Promise.all([p1, p2, p3])
promise.then(data => {
console.log('Result is: ' + data)
}).catch(error => {
console.log('Error: ' + error)
})
console.log('end')
运行结果如下:
start
end
Error: p3
程序立即输出 start
和 end
,大约3秒钟之后,输出 Error: p3
,然后又过了大约2秒钟,程序结束。
race()
方法和 all()
方法很类似,只不过是当多个内部Promise的任意一个状态发生变化( fulfilled
或者 rejected
)时,外部Promise的状态就随之变化。
例16:
console.log('start')
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(100)}, 1000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(200)}, 5000)
})
let p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(50)}, 3000)
})
let promise = Promise.race([p1, p2, p3])
promise.then(data => {
console.log('Result is: ' + data)
})
console.log('end')
运行结果如下:
start
end
Result is: 100
程序立即输出 start
和 end
,大约1秒钟之后,输出 Result is: 100
,然后又过了大约4秒钟,程序结束。
例17:
console.log('start')
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(100)}, 1000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(200)}, 5000)
})
let p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p3')}, 3000)
})
let promise = Promise.race([p1, p2, p3])
promise.then(data => {
console.log('Result is: ' + data)
}).catch(error => {
console.log('Error: ' + error)
})
console.log('end')
运行结果如下:
start
end
Result is: 100
程序立即输出 start
和 end
,大约1秒钟之后,输出 Result is: 100
,然后又过了大约4秒钟,程序结束。
虽然在p3里,3秒钟时抛出了异常,但是为时已晚,因为p1在1秒钟时resolve了,所以promise就随之resolve了。
resolve()
和 reject()
Promise.resolve(xxx)
相当于 new Promise(resolve => {resolve(xxx)})
。
Promise.reject(xxx)
相当于 new Promise((resolve, reject) => {reject(xxx)})
Promise.resolve()
的另一个作用就是将thenable对象(即带有 then()
方法的对象)转换为Promise对象。
例18:
let promise = Promise.resolve({
then: (resolve, reject) => {
resolve(123)
}
})
console.log(promise instanceof Promise)
promise.then(data => {
console.log('Result is: ' + data)
})
运行结果如下:
true
Result is: 123
Promise.resolve()
还可以resolve一个Promise对象。
例19:
let p1 = new Promise((resolve, reject) => {
console.log('start')
resolve(123)
console.log('end')
})
let p2 = Promise.resolve(p1)
p2.then(data => {
console.log('Result is: ' + data)
})
运行结果如下:
start
end
Result is: 123
then()
会返回一个新创建的Promise对象(不是原来的那个对象)。比较下面两个例子:
例20:
let promise = new Promise((resolve, reject) => {
console.log('start')
resolve(123)
console.log('end')
})
promise.then(data => {
console.log('p1: ' + data)
return data + 1000
})
promise.then(data => {
console.log('p2: ' + data)
return data + 10000
})
promise.then(data => {
console.log('p3: ' + data)
})
运行结果如下:
start
end
p1: 123
p2: 123
p3: 123
例21:
let promise = new Promise((resolve, reject) => {
console.log('start')
resolve(123)
console.log('end')
})
promise.then(data => {
console.log('p1: ' + data)
return data + 1000
})
.then(data => {
console.log('p2: ' + data)
return data + 10000
})
.then(data => {
console.log('p3: ' + data)
})
运行结果如下:
start
end
p1: 123
p2: 1123
p3: 11123
下面代码的输出结果是什么?
例22:
function f() {
let promise = new Promise((resolve, reject) => {
console.log('start')
resolve(123)
console.log('end')
})
promise.then(data => {
console.log('Inside result: ' + data)
return data + 1000
})
return promise
}
f().then(data => {
console.log('Outside result: ' + data)
})
运行结果如下:
start
end
Inside result: 123
Outside result: 123
解析:参见例20。如果想要将外部结果变成1123,则在 f()
里不能返回promise,而应该返回 promise.then()
。
下面代码的输出结果是什么?
例23:
console.log('start')
let promise = new Promise((resolve, reject) => {
reject(123)
setTimeout(() => {
resolve()
}, 0)
})
promise.then(data => {
console.log('Result is: ' + data)
}, error => {
console.log('Error: ' + error)
})
console.log('end')
运行结果如下:
start
end
Error: 123
解析:Promise构造函数是同步执行的, reject()
虽然是异步操作,但是会立即(同步)把Promise状态设置为 rejected
,而定时器是异步操作,即使时间设置为0,也会在同步操作都运行完以后才会运行。因此,Promise状态会被设置为 rejected
,之后就不会再发生变化。在同步操作运行完之后,就会执行 then()
方法里指定的onRejected回调函数。
https://www.cnblogs.com/le220/p/10381920.html
https://www.w3cschool.cn/javascript_guide/javascript_guide-a9jb269c.html
https://www.runoob.com/js/js-promise.html